From 90fdf594156a6adf39fba8de344290edb2a60e90 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Fri, 12 Jul 2024 09:39:19 +1000 Subject: [PATCH 001/206] Test current stock logic on shipment level During checkout, stock is adjusted when a shipment is finalised. The chain is: * Order state change to complete. * Trigger Order#finalize! which updates shipments. * Trigger Shipment#finalize! which adjusts stock on the variant. * A variant holds stock in stock items or in a variant override. --- spec/models/spree/shipment_spec.rb | 31 ++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/spec/models/spree/shipment_spec.rb b/spec/models/spree/shipment_spec.rb index a559d8b855..ecaba70205 100644 --- a/spec/models/spree/shipment_spec.rb +++ b/spec/models/spree/shipment_spec.rb @@ -264,6 +264,37 @@ RSpec.describe Spree::Shipment do end end + describe "#finalize!" do + subject(:shipment) { order.shipments.first } + let(:variant) { order.variants.first } + let(:order) { create(:order_ready_for_confirmation) } + + it "reduces stock" do + variant.on_hand = 5 + + expect { shipment.finalize! } + .to change { variant.on_hand }.from(5).to(4) + end + + it "reduces stock of a variant override" do + variant.on_hand = 5 + variant_override = VariantOverride.create!( + variant:, + hub: order.distributor, + count_on_hand: 7, + on_demand: false, + ) + + expect { + shipment.finalize! + variant.reload + variant_override.reload + } + .to change { variant_override.count_on_hand }.from(7).to(6) + .and change { variant.on_hand }.by(0) + end + end + context "when order is completed" do before do allow(order).to receive_messages completed?: true From 675b7febdf16cfffb00a0579f34494c7eb2bd785 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Fri, 12 Jul 2024 15:17:35 +1000 Subject: [PATCH 002/206] Test stock logic on variant level VariantOverrides are bolted onto variants to change their logic. --- spec/models/spree/variant_stock_spec.rb | 89 +++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 spec/models/spree/variant_stock_spec.rb diff --git a/spec/models/spree/variant_stock_spec.rb b/spec/models/spree/variant_stock_spec.rb new file mode 100644 index 0000000000..137d130402 --- /dev/null +++ b/spec/models/spree/variant_stock_spec.rb @@ -0,0 +1,89 @@ +# frozen_string_literal: false + +require 'spec_helper' + +RSpec.describe Spree::Variant do + # This method is defined in app/models/concerns/variant_stock.rb. + # There is a separate spec for that concern but here I want to test + # the interplay of Spree::Variant and VariantOverride. + # + # A variant can be scoped to a hub which means that all stock methods + # like this one get overridden. Future calls to `variant.move` are then + # handled by the ScopeVariantToHub module which may call the + # VariantOverride. + describe "#move" do + subject(:variant) { create(:variant, on_hand: 5) } + + it "changes stock" do + expect { variant.move(-2) }.to change { variant.on_hand }.from(5).to(3) + end + + it "ignores stock when on demand" do + variant.on_demand = true + + expect { variant.move(-2) }.not_to change { variant.on_hand } + end + + it "rejects negative stock" do + expect { variant.move(-7) }.to raise_error( + ActiveRecord::RecordInvalid, + "Validation failed: Count on hand must be greater than or equal to 0" + ) + end + + describe "with VariantOverride" do + subject(:hub_variant) { + Spree::Variant.find(variant.id).tap { |v| scoper.scope(v) } + } + let(:override) { + VariantOverride.create!( + variant:, + hub: create(:distributor_enterprise), + count_on_hand: 7, + on_demand: false, + ) + } + let(:scoper) { OpenFoodNetwork::ScopeVariantToHub.new(override.hub) } + + it "changes stock only on the variant override" do + expect { + hub_variant.move(-3) + override.reload + } + .to change { override.count_on_hand }.from(7).to(4) + .and change { hub_variant.on_hand }.from(7).to(4) + .and change { variant.on_hand }.by(0) + end + + it "ignores stock when on demand" do + override.update!(on_demand: true, count_on_hand: nil) + + expect { + hub_variant.move(-3) + override.reload + } + .not_to change { + [ + override.count_on_hand, + hub_variant.on_hand, + variant.on_hand, + ] + } + end + + it "doesn't prevent negative stock" do + # VariantOverride relies on other stock checks during checkout. :-( + expect { + hub_variant.move(-8) + override.reload + } + .to change { override.count_on_hand }.from(7).to(-1) + .and change { hub_variant.on_hand }.from(7).to(-1) + .and change { variant.on_hand }.by(0) + + # The update didn't run validations and now it's invalid: + expect(override).not_to be_valid + end + end + end +end From e9f89362f4ebf43d1921981621c80f0cde3a5d9d Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Wed, 20 Mar 2024 16:39:35 +1100 Subject: [PATCH 003/206] Remove validation of positive stock when on demand We weren't allowing negative stock to stop any bug from accidentally drawing too much stock. But now we want to implement a backordering logic that depends on negative stock levels to know how much is needed to replenish stock levels. --- app/models/variant_override.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/models/variant_override.rb b/app/models/variant_override.rb index cabc309a93..bc1afbbf4e 100644 --- a/app/models/variant_override.rb +++ b/app/models/variant_override.rb @@ -15,7 +15,9 @@ class VariantOverride < ApplicationRecord # Need to ensure this can be set by the user. validates :default_stock, numericality: { greater_than_or_equal_to: 0 }, allow_nil: true validates :price, numericality: { greater_than_or_equal_to: 0 }, allow_nil: true - validates :count_on_hand, numericality: { greater_than_or_equal_to: 0 }, allow_nil: true + validates :count_on_hand, numericality: { + greater_than_or_equal_to: 0, unless: :on_demand? + }, allow_nil: true default_scope { where(permission_revoked_at: nil) } From a1887bdc7684cfc65384715d4c2d44e6d5d97ea9 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Wed, 20 Mar 2024 16:53:39 +1100 Subject: [PATCH 004/206] Update stock levels of on-demand items We weren't bothering with stock when items were on demand anyway. But we want to track stock now so that we can backorder more when local stock levels become negative. --- app/models/concerns/variant_stock.rb | 3 +-- lib/open_food_network/scope_variant_to_hub.rb | 4 ---- spec/lib/open_food_network/scope_variant_to_hub_spec.rb | 2 ++ spec/models/spree/line_item_spec.rb | 4 ++-- spec/system/consumer/shopping/variant_overrides_spec.rb | 2 ++ 5 files changed, 7 insertions(+), 8 deletions(-) diff --git a/app/models/concerns/variant_stock.rb b/app/models/concerns/variant_stock.rb index f39ab38dc9..10ea8f136e 100644 --- a/app/models/concerns/variant_stock.rb +++ b/app/models/concerns/variant_stock.rb @@ -112,8 +112,7 @@ module VariantStock # # This enables us to override this behaviour for variant overrides def move(quantity, originator = nil) - # Don't change variant stock if variant is on_demand or has been deleted - return if on_demand || deleted_at + return if deleted_at raise_error_if_no_stock_item_available diff --git a/lib/open_food_network/scope_variant_to_hub.rb b/lib/open_food_network/scope_variant_to_hub.rb index d7a29cbb9c..90bfef39d4 100644 --- a/lib/open_food_network/scope_variant_to_hub.rb +++ b/lib/open_food_network/scope_variant_to_hub.rb @@ -43,11 +43,7 @@ module OpenFoodNetwork # - updates variant_override.count_on_hand # - does not create stock_movement # - does not update stock_item.count_on_hand - # If it is a variant override with on_demand: - # - don't change stock or call super (super would change the variant's stock) def move(quantity, originator = nil) - return if @variant_override&.on_demand - if @variant_override&.stock_overridden? @variant_override.move_stock! quantity else diff --git a/spec/lib/open_food_network/scope_variant_to_hub_spec.rb b/spec/lib/open_food_network/scope_variant_to_hub_spec.rb index cbff22dc35..705f4ba159 100644 --- a/spec/lib/open_food_network/scope_variant_to_hub_spec.rb +++ b/spec/lib/open_food_network/scope_variant_to_hub_spec.rb @@ -182,6 +182,8 @@ module OpenFoodNetwork end it "doesn't reduce variant's stock" do + pending "updating override stock" + v2.move(-2) expect(Spree::Variant.find(v2.id).on_hand).to eq 5 end diff --git a/spec/models/spree/line_item_spec.rb b/spec/models/spree/line_item_spec.rb index d715fec503..b6fecf1f33 100644 --- a/spec/models/spree/line_item_spec.rb +++ b/spec/models/spree/line_item_spec.rb @@ -313,8 +313,8 @@ module Spree expect(order.shipment.manifest.first.variant).to eq line_item.variant end - it "does not reduce the variant's stock level" do - expect(variant_on_demand.reload.on_hand).to eq 1 + it "reduces the variant's stock level" do + expect(variant_on_demand.reload.on_hand).to eq(-9) end it "does not mark inventory units as backorderd" do diff --git a/spec/system/consumer/shopping/variant_overrides_spec.rb b/spec/system/consumer/shopping/variant_overrides_spec.rb index 01dd56c349..2bbc679604 100644 --- a/spec/system/consumer/shopping/variant_overrides_spec.rb +++ b/spec/system/consumer/shopping/variant_overrides_spec.rb @@ -223,6 +223,8 @@ RSpec.describe "shopping with variant overrides defined" do end it "does not subtract stock from variants where the override has on_demand: true" do + pending "update override stock" + click_add_to_cart product4_variant1, 2 click_checkout expect do From cd8dc41b15859cbd86fd95687e7a97df5d6b17dc Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Fri, 12 Jul 2024 12:00:48 +1000 Subject: [PATCH 005/206] Update stock specs and add pending cases --- spec/models/spree/variant_stock_spec.rb | 34 +++++++++++++++++-------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/spec/models/spree/variant_stock_spec.rb b/spec/models/spree/variant_stock_spec.rb index 137d130402..b36f989645 100644 --- a/spec/models/spree/variant_stock_spec.rb +++ b/spec/models/spree/variant_stock_spec.rb @@ -18,10 +18,10 @@ RSpec.describe Spree::Variant do expect { variant.move(-2) }.to change { variant.on_hand }.from(5).to(3) end - it "ignores stock when on demand" do + it "reduces stock even when on demand" do variant.on_demand = true - expect { variant.move(-2) }.not_to change { variant.on_hand } + expect { variant.move(-2) }.to change { variant.on_hand }.from(5).to(3) end it "rejects negative stock" do @@ -55,20 +55,18 @@ RSpec.describe Spree::Variant do .and change { variant.on_hand }.by(0) end - it "ignores stock when on demand" do - override.update!(on_demand: true, count_on_hand: nil) + it "reduces stock when on demand" do + pending "VariantOverride allowing stock with on_demand" + + override.update!(on_demand: true, count_on_hand: 7) expect { hub_variant.move(-3) override.reload } - .not_to change { - [ - override.count_on_hand, - hub_variant.on_hand, - variant.on_hand, - ] - } + .to change { override.count_on_hand }.from(7).to(4) + .and change { hub_variant.on_hand }.from(7).to(4) + .and change { variant.on_hand }.by(0) end it "doesn't prevent negative stock" do @@ -84,6 +82,20 @@ RSpec.describe Spree::Variant do # The update didn't run validations and now it's invalid: expect(override).not_to be_valid end + + it "doesn't fail on negative stock when on demand" do + pending "https://github.com/openfoodfoundation/openfoodnetwork/issues/12586" + + override.update!(on_demand: true, count_on_hand: nil) + + expect { + hub_variant.move(-8) + override.reload + } + .to change { override.count_on_hand }.from(7).to(-1) + .and change { hub_variant.on_hand }.from(7).to(-1) + .and change { variant.on_hand }.by(0) + end end end end From b6c407971dc9a5972cc44591cf54a20ff6939a9e Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Wed, 31 Jul 2024 15:26:53 +1000 Subject: [PATCH 006/206] Allow on-demand VariantOverride to track stock We allowed this for producer stock and need to do the same for inventory stock. This will allow us to create backorders for missing, but promised stock. --- .../stock_settings_override_validation.rb | 13 ++------- config/locales/en.yml | 1 - spec/models/spree/variant_stock_spec.rb | 2 -- spec/models/variant_override_spec.rb | 7 ++--- spec/system/admin/product_import_spec.rb | 27 ------------------- 5 files changed, 4 insertions(+), 46 deletions(-) diff --git a/app/models/concerns/stock_settings_override_validation.rb b/app/models/concerns/stock_settings_override_validation.rb index 7040cb50e0..b2036991d3 100644 --- a/app/models/concerns/stock_settings_override_validation.rb +++ b/app/models/concerns/stock_settings_override_validation.rb @@ -6,14 +6,14 @@ # `count_on_hand` can either be: nil or a number # # This means that a variant override can be in six different stock states -# but only three of them are valid. +# but only four of them are valid. # # | on_demand | count_on_hand | stock_overridden? | use_producer_stock_settings? | valid? | # |-----------|---------------|-------------------|------------------------------|--------| # | 1 | nil | false | false | true | # | 0 | x | true | false | true | # | nil | nil | false | true | true | -# | 1 | x | ? | ? | false | +# | 1 | x | true | false | true | # | 0 | nil | ? | ? | false | # | nil | x | ? | ? | false | # @@ -27,7 +27,6 @@ module StockSettingsOverrideValidation def require_compatible_on_demand_and_count_on_hand disallow_count_on_hand_if_using_producer_stock_settings - disallow_count_on_hand_if_on_demand require_count_on_hand_if_limited_stock end @@ -39,14 +38,6 @@ module StockSettingsOverrideValidation errors.add(:count_on_hand, error_message) end - def disallow_count_on_hand_if_on_demand - return unless on_demand? && count_on_hand.present? - - error_message = I18n.t("count_on_hand.on_demand_but_count_on_hand_set", - scope: i18n_scope_for_stock_settings_override_validation_error) - errors.add(:count_on_hand, error_message) - end - def require_count_on_hand_if_limited_stock return unless on_demand == false && count_on_hand.blank? diff --git a/config/locales/en.yml b/config/locales/en.yml index f2f0ed92ab..d5743302fa 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -127,7 +127,6 @@ en: variant_override: count_on_hand: using_producer_stock_settings_but_count_on_hand_set: "must be blank because using producer stock settings" - on_demand_but_count_on_hand_set: "must be blank if on demand" limited_stock_but_no_count_on_hand: "must be specified because forcing limited stock" messages: confirmation: "doesn't match %{attribute}" diff --git a/spec/models/spree/variant_stock_spec.rb b/spec/models/spree/variant_stock_spec.rb index b36f989645..4a999fac6c 100644 --- a/spec/models/spree/variant_stock_spec.rb +++ b/spec/models/spree/variant_stock_spec.rb @@ -56,8 +56,6 @@ RSpec.describe Spree::Variant do end it "reduces stock when on demand" do - pending "VariantOverride allowing stock with on_demand" - override.update!(on_demand: true, count_on_hand: 7) expect { diff --git a/spec/models/variant_override_spec.rb b/spec/models/variant_override_spec.rb index 15c9a1f4bf..38022e206e 100644 --- a/spec/models/variant_override_spec.rb +++ b/spec/models/variant_override_spec.rb @@ -97,11 +97,8 @@ RSpec.describe VariantOverride do context "when count_on_hand is set" do let(:count_on_hand) { 1 } - it "is invalid" do - expect(variant_override).not_to be_valid - error_message = I18n.t("on_demand_but_count_on_hand_set", - scope: [i18n_scope_for_error, "count_on_hand"]) - expect(variant_override.errors[:count_on_hand]).to eq([error_message]) + it "is valid" do + expect(variant_override).to be_valid end end end diff --git a/spec/system/admin/product_import_spec.rb b/spec/system/admin/product_import_spec.rb index e916a56a74..49c9069918 100644 --- a/spec/system/admin/product_import_spec.rb +++ b/spec/system/admin/product_import_spec.rb @@ -624,33 +624,6 @@ RSpec.describe "Product Import" do expect(page).not_to have_content "line 3: Sprouts" end - it "handles on_demand and on_hand validations with inventory - With both values set" do - csv_data = <<~CSV - name, distributor, producer, category, on_hand, price, units, on_demand - Beans, Another Enterprise, User Enterprise, Vegetables, 6, 3.20, 500, 1 - Sprouts, Another Enterprise, User Enterprise, Vegetables, 6, 6.50, 500, 1 - Cabbage, Another Enterprise, User Enterprise, Vegetables, 0, 1.50, 500, 1 - CSV - File.write('/tmp/test.csv', csv_data) - - visit main_app.admin_product_import_path - select 'Inventories', from: "settings_import_into" - attach_file 'file', '/tmp/test.csv' - click_button 'Upload' - - proceed_to_validation - - expect(page).to have_selector '.item-count', text: "3" - expect(page).to have_selector '.invalid-count', text: "3" - - find('div.header-description', text: 'Items contain errors').click - expect(page).to have_content "line 2: Beans - Count_on_hand must be blank if on demand" - expect(page).to have_content "line 3: Sprouts - Count_on_hand must be blank if on demand" - expect(page).to have_content "line 4: Cabbage - Count_on_hand must be blank if on demand" - expect(page).to have_content "Imported file contains invalid entries" - expect(page).not_to have_selector 'input[type=submit][value="Save"]' - end - it "imports lines with all allowed units" do csv_data = CSV.generate do |csv| csv << ["name", "producer", "category", "on_hand", "price", "units", "unit_type", From 2201d2e8c24322b1b21c3c1481c033984fb73817 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Wed, 31 Jul 2024 16:48:50 +1000 Subject: [PATCH 007/206] VariantOverride with on_demand now overriding stock Otherwise we would try to take stock from the producer stock level without respecting their on-demand settings. So from now on: If stock level or on_demand are set on the override then it's not using producer stock levels. --- .../concerns/stock_settings_override_validation.rb | 2 +- app/models/concerns/variant_stock.rb | 2 +- app/models/variant_override.rb | 5 ++--- .../open_food_network/scope_variant_to_hub_spec.rb | 5 ++--- spec/models/spree/variant_stock_spec.rb | 6 ++---- spec/models/variant_override_spec.rb | 13 ++++++++----- .../consumer/shopping/variant_overrides_spec.rb | 6 ++---- 7 files changed, 18 insertions(+), 21 deletions(-) diff --git a/app/models/concerns/stock_settings_override_validation.rb b/app/models/concerns/stock_settings_override_validation.rb index b2036991d3..ecc3f2ec3b 100644 --- a/app/models/concerns/stock_settings_override_validation.rb +++ b/app/models/concerns/stock_settings_override_validation.rb @@ -10,7 +10,7 @@ # # | on_demand | count_on_hand | stock_overridden? | use_producer_stock_settings? | valid? | # |-----------|---------------|-------------------|------------------------------|--------| -# | 1 | nil | false | false | true | +# | 1 | nil | true | false | true | # | 0 | x | true | false | true | # | nil | nil | false | true | true | # | 1 | x | true | false | true | diff --git a/app/models/concerns/variant_stock.rb b/app/models/concerns/variant_stock.rb index 10ea8f136e..2e5023d46c 100644 --- a/app/models/concerns/variant_stock.rb +++ b/app/models/concerns/variant_stock.rb @@ -96,7 +96,7 @@ module VariantStock # Here we depend only on variant.total_on_hand and variant.on_demand. # This way, variant_overrides only need to override variant.total_on_hand and variant.on_demand. def fill_status(quantity) - on_hand = if total_on_hand >= quantity || on_demand + on_hand = if total_on_hand.to_i >= quantity || on_demand quantity else [0, total_on_hand].max diff --git a/app/models/variant_override.rb b/app/models/variant_override.rb index bc1afbbf4e..79a28508f2 100644 --- a/app/models/variant_override.rb +++ b/app/models/variant_override.rb @@ -38,9 +38,8 @@ class VariantOverride < ApplicationRecord end def stock_overridden? - # If count_on_hand is present, it means on_demand is false - # See StockSettingsOverrideValidation for details - count_on_hand.present? + # Testing for not nil because for a boolean `false.present?` is false. + !on_demand.nil? || !count_on_hand.nil? end def use_producer_stock_settings? diff --git a/spec/lib/open_food_network/scope_variant_to_hub_spec.rb b/spec/lib/open_food_network/scope_variant_to_hub_spec.rb index 705f4ba159..faa5d0583b 100644 --- a/spec/lib/open_food_network/scope_variant_to_hub_spec.rb +++ b/spec/lib/open_food_network/scope_variant_to_hub_spec.rb @@ -181,11 +181,10 @@ module OpenFoodNetwork scoper.scope v2 end - it "doesn't reduce variant's stock" do - pending "updating override stock" - + it "reduces override stock, not variant's stock" do v2.move(-2) expect(Spree::Variant.find(v2.id).on_hand).to eq 5 + expect(v2.on_hand).to eq(-2) end end diff --git a/spec/models/spree/variant_stock_spec.rb b/spec/models/spree/variant_stock_spec.rb index 4a999fac6c..224fa3e855 100644 --- a/spec/models/spree/variant_stock_spec.rb +++ b/spec/models/spree/variant_stock_spec.rb @@ -82,16 +82,14 @@ RSpec.describe Spree::Variant do end it "doesn't fail on negative stock when on demand" do - pending "https://github.com/openfoodfoundation/openfoodnetwork/issues/12586" - override.update!(on_demand: true, count_on_hand: nil) expect { hub_variant.move(-8) override.reload } - .to change { override.count_on_hand }.from(7).to(-1) - .and change { hub_variant.on_hand }.from(7).to(-1) + .to change { override.count_on_hand }.from(nil).to(-8) + .and change { hub_variant.on_hand }.from(nil).to(-8) .and change { variant.on_hand }.by(0) end end diff --git a/spec/models/variant_override_spec.rb b/spec/models/variant_override_spec.rb index 38022e206e..41122e6e71 100644 --- a/spec/models/variant_override_spec.rb +++ b/spec/models/variant_override_spec.rb @@ -165,7 +165,7 @@ RSpec.describe VariantOverride do describe "with nil count on hand" do let(:variant_override) do - build_stubbed( + build( :variant_override, variant: build_stubbed(:variant), hub: build_stubbed(:distributor_enterprise), @@ -175,15 +175,18 @@ RSpec.describe VariantOverride do end describe "stock_overridden?" do - it "returns false" do - expect(variant_override.stock_overridden?).to be false + it "returns true" do + expect(variant_override.stock_overridden?).to be true end end describe "move_stock!" do it "silently logs an error" do - expect(Bugsnag).to receive(:notify) - variant_override.move_stock!(5) + expect { + variant_override.move_stock!(5) + }.to change { + variant_override.count_on_hand + }.from(nil).to(5) end end end diff --git a/spec/system/consumer/shopping/variant_overrides_spec.rb b/spec/system/consumer/shopping/variant_overrides_spec.rb index 2bbc679604..4cc8d11eb0 100644 --- a/spec/system/consumer/shopping/variant_overrides_spec.rb +++ b/spec/system/consumer/shopping/variant_overrides_spec.rb @@ -222,15 +222,13 @@ RSpec.describe "shopping with variant overrides defined" do expect(product1_variant1_override.reload.count_on_hand).to be_nil end - it "does not subtract stock from variants where the override has on_demand: true" do - pending "update override stock" - + it "subtracts stock from override but not variants where the override has on_demand: true" do click_add_to_cart product4_variant1, 2 click_checkout expect do complete_checkout end.to change { product4_variant1.reload.on_hand }.by(0) - expect(product4_variant1_override.reload.count_on_hand).to be_nil + expect(product4_variant1_override.reload.count_on_hand).to eq(-2) end it "does not show out of stock flags on order confirmation page" do From 2d245934030e932a6c79e524d485c7241172db27 Mon Sep 17 00:00:00 2001 From: wandji20 Date: Fri, 2 Aug 2024 13:03:42 +0100 Subject: [PATCH 008/206] Update product variant unit display name, price, and total price widths on different screen sizes [OFN-6567] --- app/views/shop/products/_shop_variant.html.haml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/views/shop/products/_shop_variant.html.haml b/app/views/shop/products/_shop_variant.html.haml index b6e91c8c2f..bd2c226aa8 100644 --- a/app/views/shop/products/_shop_variant.html.haml +++ b/app/views/shop/products/_shop_variant.html.haml @@ -1,8 +1,8 @@ = cache_with_locale do - .small-4.medium-4.large-5.columns.variant-name + .small-3.columns.variant-name .inline{"ng-if" => "::variant.display_name"} {{ ::variant.display_name }} .variant-unit {{ ::variant.unit_to_display }} - .small-3.medium-3.large-2.columns.variant-price + .small-4.medium-3.columns.variant-price %price-breakdown{"price-breakdown" => "_", variant: "variant", "price-breakdown-append-to-body" => "true", "price-breakdown-placement" => "bottom", @@ -16,7 +16,7 @@ key: "'js.shopfront.unit_price_tooltip'"} {{ variant.unit_price_price | localizeCurrency }} / {{ variant.unit_price_unit }} - .medium-2.large-2.columns.total-price + .medium-3.columns.total-price %span{"ng-class" => "{filled: variant.line_item.total_price}"} {{ variant.line_item.total_price | localizeCurrency }} = render partial: "shop/products/shop_variant_no_group_buy" From c101c4e42fbab48ee779ace31541ed9d814f4936 Mon Sep 17 00:00:00 2001 From: Ahmed Ejaz Date: Mon, 5 Aug 2024 13:51:59 +0500 Subject: [PATCH 009/206] 12698 - fix 'go back to products' stateful navigation --- app/controllers/admin/products_v3_controller.rb | 2 ++ app/controllers/spree/admin/products_controller.rb | 1 + app/helpers/admin/products_helper.rb | 4 ++++ app/views/spree/admin/products/edit.html.haml | 4 ++-- 4 files changed, 9 insertions(+), 2 deletions(-) diff --git a/app/controllers/admin/products_v3_controller.rb b/app/controllers/admin/products_v3_controller.rb index 327a4a4be9..24ed032dcf 100644 --- a/app/controllers/admin/products_v3_controller.rb +++ b/app/controllers/admin/products_v3_controller.rb @@ -11,6 +11,8 @@ module Admin def index fetch_products render "index", locals: { producers:, categories:, tax_category_options:, flash: } + + session[:products_return_to_url] = request.url end def bulk_update diff --git a/app/controllers/spree/admin/products_controller.rb b/app/controllers/spree/admin/products_controller.rb index 3ae92a38bd..4580a751f0 100644 --- a/app/controllers/spree/admin/products_controller.rb +++ b/app/controllers/spree/admin/products_controller.rb @@ -10,6 +10,7 @@ module Spree include OpenFoodNetwork::SpreeApiKeyLoader include OrderCyclesHelper include EnterprisesHelper + helper ::Admin::ProductsHelper before_action :load_data before_action :load_producers, only: [:index, :new] diff --git a/app/helpers/admin/products_helper.rb b/app/helpers/admin/products_helper.rb index d0d5611d80..334743a62b 100644 --- a/app/helpers/admin/products_helper.rb +++ b/app/helpers/admin/products_helper.rb @@ -27,5 +27,9 @@ module Admin [precised_unit_value, variant.unit_description].compact_blank.join(" ") end + + def products_return_to_url + session[:products_return_to_url] || admin_products_url + end end end diff --git a/app/views/spree/admin/products/edit.html.haml b/app/views/spree/admin/products/edit.html.haml index 6a5ccb4c21..f30115860c 100644 --- a/app/views/spree/admin/products/edit.html.haml +++ b/app/views/spree/admin/products/edit.html.haml @@ -1,7 +1,7 @@ = admin_inject_available_units - content_for :page_actions do - %li= button_link_to t('admin.products.back_to_products_list'), "#{admin_products_path}#{(@url_filters.empty? ? "" : "#?#{@url_filters.to_query}")}", :icon => 'icon-arrow-left' + %li= button_link_to t('admin.products.back_to_products_list'), products_return_to_url, :icon => 'icon-arrow-left' %li#new_product_link = button_link_to t(:new_product), new_object_url, { :icon => 'icon-plus', :id => 'admin_new_product' } @@ -16,4 +16,4 @@ .form-buttons.filter-actions.actions = button t(:update), 'icon-refresh' - = button_link_to t(:cancel), "#{collection_url}#{(@url_filters.empty? ? "" : "#?#{@url_filters.to_query}")}", icon: 'icon-remove' + = button_link_to t(:cancel), products_return_to_url, icon: 'icon-remove' From b49da468424b3970e866e92914e7a0343f3a93dd Mon Sep 17 00:00:00 2001 From: wandji20 Date: Tue, 6 Aug 2024 08:52:51 +0100 Subject: [PATCH 010/206] Pluralize admin products search result [OFN-12532-v1] --- app/views/admin/products_v3/_sort.html.haml | 2 +- config/locales/en.yml | 4 +++- spec/system/admin/products_v3/index_spec.rb | 12 ++++++------ 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/app/views/admin/products_v3/_sort.html.haml b/app/views/admin/products_v3/_sort.html.haml index 4cb5f29ee9..2b25644639 100644 --- a/app/views/admin/products_v3/_sort.html.haml +++ b/app/views/admin/products_v3/_sort.html.haml @@ -1,7 +1,7 @@ #sort %div.pagination-description - if pagy.present? - = t(".pagination.total_html", total: pagy.count, from: pagy.from, to: pagy.to) + = t(".pagination.products_total_html", count: pagy.count, from: pagy.from, to: pagy.to) - if search_term.present? || producer_id.present? || category_id.present? %a{ href: url_for(page: 1), class: "button disruptive", data: { 'turbo-frame': "_self", 'turbo-action': "advance" } } diff --git a/config/locales/en.yml b/config/locales/en.yml index 83735ca5af..dae18c5d83 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -880,7 +880,9 @@ en: search: Search sort: pagination: - total_html: "%{total} products found for your search criteria. Showing %{from} to %{to}." + products_total_html: + one: "%{count} product found for your search criteria. Showing %{from} to %{to}." + other: "%{count} products found for your search criteria. Showing %{from} to %{to}." per_page: show: Show per_page: "%{num} per page" diff --git a/spec/system/admin/products_v3/index_spec.rb b/spec/system/admin/products_v3/index_spec.rb index f19172894a..3d5f30c03f 100644 --- a/spec/system/admin/products_v3/index_spec.rb +++ b/spec/system/admin/products_v3/index_spec.rb @@ -197,7 +197,7 @@ RSpec.describe 'As an enterprise user, I can manage my products' do search_for "searchable product" expect(page).to have_field "search_term", with: "searchable product" - expect(page).to have_content "1 products found for your search criteria. Showing 1 to 1." + expect(page).to have_content "1 product found for your search criteria. Showing 1 to 1." expect_products_count_to_be 1 end @@ -216,7 +216,7 @@ RSpec.describe 'As an enterprise user, I can manage my products' do search_for "searchable product" expect(page).to have_field "search_term", with: "searchable product" - expect(page).to have_content "1 products found for your search criteria. Showing 1 to 1." + expect(page).to have_content "1 product found for your search criteria. Showing 1 to 1." expect_products_count_to_be 1 end @@ -229,7 +229,7 @@ RSpec.describe 'As an enterprise user, I can manage my products' do search_for "Big box" expect(page).to have_field "search_term", with: "Big box" - expect(page).to have_content "1 products found for your search criteria. Showing 1 to 1." + expect(page).to have_content "1 product found for your search criteria. Showing 1 to 1." expect_products_count_to_be 1 end @@ -246,7 +246,7 @@ RSpec.describe 'As an enterprise user, I can manage my products' do expect_per_page_to_be 15 expect_products_count_to_be 1 search_for "searchable product" - expect(page).to have_content "1 products found for your search criteria. Showing 1 to 1." + expect(page).to have_content "1 product found for your search criteria. Showing 1 to 1." expect_products_count_to_be 1 end @@ -256,7 +256,7 @@ RSpec.describe 'As an enterprise user, I can manage my products' do search_for "searchable product" expect(page).to have_field "search_term", with: "searchable product" - expect(page).to have_content "1 products found for your search criteria. Showing 1 to 1." + expect(page).to have_content "1 product found for your search criteria. Showing 1 to 1." expect_products_count_to_be 1 expect(page).to have_field "Name", with: product_by_name.name @@ -348,7 +348,7 @@ RSpec.describe 'As an enterprise user, I can manage my products' do search_by_category "Category 1" - expect(page).to have_content "1 products found for your search criteria. Showing 1 to 1." + expect(page).to have_content "1 product found for your search criteria. Showing 1 to 1." expect(page).to have_select "category_id", selected: "Category 1" expect_products_count_to_be 1 expect(page).to have_field "Name", with: product_by_category.name From a2f4df191a7e650a1f0838b50e1a5ab5f136ff4a Mon Sep 17 00:00:00 2001 From: Joseph Johansen Date: Tue, 6 Aug 2024 15:30:21 +0100 Subject: [PATCH 011/206] Improve effiency of OrderCycle.earliest_closing_times --- app/models/order_cycle.rb | 5 ++--- app/serializers/api/uncached_enterprise_serializer.rb | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/app/models/order_cycle.rb b/app/models/order_cycle.rb index 060f75e4f0..3fa6448944 100644 --- a/app/models/order_cycle.rb +++ b/app/models/order_cycle.rb @@ -154,9 +154,8 @@ class OrderCycle < ApplicationRecord joins(:order_cycle). merge(OrderCycle.active). group('exchanges.receiver_id'). - select("exchanges.receiver_id AS receiver_id, - MIN(order_cycles.orders_close_at) AS earliest_close_at"). - map { |ex| [ex.receiver_id, ex.earliest_close_at.to_time] } + pluck(Arel.sql("exchanges.receiver_id AS receiver_id"), + Arel.sql("MIN(order_cycles.orders_close_at) AS earliest_close_at")) ] end diff --git a/app/serializers/api/uncached_enterprise_serializer.rb b/app/serializers/api/uncached_enterprise_serializer.rb index d92db46d28..c1d620facd 100644 --- a/app/serializers/api/uncached_enterprise_serializer.rb +++ b/app/serializers/api/uncached_enterprise_serializer.rb @@ -7,7 +7,7 @@ module Api attributes :orders_close_at, :active def orders_close_at - options[:data].earliest_closing_times[object.id] + options[:data].earliest_closing_times[object.id]&.to_time end def active From 5ca7f40a4e67d6d68e9d1a1fc41b5780ea20c045 Mon Sep 17 00:00:00 2001 From: Joseph Johansen Date: Tue, 6 Aug 2024 16:09:08 +0100 Subject: [PATCH 012/206] Add unit test --- .../uncached_enterprise_serializer_spec.rb | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 spec/serializers/api/uncached_enterprise_serializer_spec.rb diff --git a/spec/serializers/api/uncached_enterprise_serializer_spec.rb b/spec/serializers/api/uncached_enterprise_serializer_spec.rb new file mode 100644 index 0000000000..b86ed91955 --- /dev/null +++ b/spec/serializers/api/uncached_enterprise_serializer_spec.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Api::UncachedEnterpriseSerializer do + let(:serializer) { + described_class.new enterprise, { data: OpenFoodNetwork::EnterpriseInjectionData.new } + } + let(:enterprise) { create :enterprise } + + before do + allow_any_instance_of(OpenFoodNetwork::EnterpriseInjectionData).to( + receive(:earliest_closing_times). + and_return(data) + ) + end + + describe '#orders_close_at' do + context "for an enterprise with an active order cycle" do + let(:order_cycle) { create :open_order_cycle, coordinator: enterprise } + let(:data) { { enterprise.id => order_cycle.orders_close_at } } + + it "returns a closing time for an enterprise" do + expect(serializer.orders_close_at).to eq order_cycle.orders_close_at + end + end + + context "for an enterprise without an active order cycle" do + let(:data) { {} } + + it "returns nil for an enterprise without a closing time" do + expect(serializer.orders_close_at).to be_nil + end + end + end +end From 6d03a8ddf38c44d377eb3fac3c66050e0fff6de2 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Fri, 2 Aug 2024 15:30:27 +1000 Subject: [PATCH 013/206] Test that the FDC is now complying with the DFC --- .../dfc_provider/app/services/fdc_request.rb | 29 +----- .../spec/services/fdc_request_spec.rb | 5 +- .../imports_from_a_FDC_catalog.yml | 98 +++++++++---------- ...e_access_token_and_retrieves_a_catalog.yml | 98 +++++++++---------- spec/system/admin/dfc_product_import_spec.rb | 2 +- 5 files changed, 96 insertions(+), 136 deletions(-) diff --git a/engines/dfc_provider/app/services/fdc_request.rb b/engines/dfc_provider/app/services/fdc_request.rb index 5252cff85a..6bb55926a8 100644 --- a/engines/dfc_provider/app/services/fdc_request.rb +++ b/engines/dfc_provider/app/services/fdc_request.rb @@ -1,33 +1,10 @@ # frozen_string_literal: true -require "private_address_check" -require "private_address_check/tcpsocket_ext" - # Request a JSON document from the FDC API with authentication. # -# Currently, the API doesn't quite comply with the DFC standard and we need -# to authenticate a little bit differently. +# This class was created when the FDC didn't comply with the DFC standard. +# But now it does and this class is empty. :-) # -# And then we get slightly different data as well. +# We can delete this in the next commit. class FdcRequest < DfcRequest - # Override main method to POST authorization data. - def call(url, data = {}) - response = request(url, auth_data.merge(data).to_json) - - if response.status >= 400 && token_stale? - refresh_access_token! - response = request(url, auth_data.merge(data).to_json) - end - - response.body - end - - private - - def auth_data - { - userId: @user.oidc_account.uid, - accessToken: @user.oidc_account.token, - } - end end diff --git a/engines/dfc_provider/spec/services/fdc_request_spec.rb b/engines/dfc_provider/spec/services/fdc_request_spec.rb index c1f59d40f8..fa0c7990bc 100644 --- a/engines/dfc_provider/spec/services/fdc_request_spec.rb +++ b/engines/dfc_provider/spec/services/fdc_request_spec.rb @@ -8,7 +8,7 @@ RSpec.describe FdcRequest do let(:user) { build(:oidc_user) } let(:account) { user.oidc_account } let(:url) { - "https://food-data-collaboration-produc-fe870152f634.herokuapp.com/fdc/products?shop=test-hodmedod.myshopify.com" + "https://env-0105831.jcloud-ver-jpe.ik-server.com/api/dfc/Enterprises/test-hodmedod/SuppliedProducts" } it "refreshes the access token and retrieves a catalog", vcr: true do @@ -27,9 +27,8 @@ RSpec.describe FdcRequest do } json = JSON.parse(response) - expect(json["message"]).to eq "Products retrieved successfully" - graph = DfcIo.import(json["products"]) + graph = DfcIo.import(json) products = graph.select { |s| s.semanticType == "dfc-b:SuppliedProduct" } expect(products).to be_present end diff --git a/spec/fixtures/vcr_cassettes/DFC_Product_Import/imports_from_a_FDC_catalog.yml b/spec/fixtures/vcr_cassettes/DFC_Product_Import/imports_from_a_FDC_catalog.yml index ad4af0b09b..3b476e79da 100644 --- a/spec/fixtures/vcr_cassettes/DFC_Product_Import/imports_from_a_FDC_catalog.yml +++ b/spec/fixtures/vcr_cassettes/DFC_Product_Import/imports_from_a_FDC_catalog.yml @@ -1,11 +1,11 @@ --- http_interactions: - request: - method: post - uri: https://food-data-collaboration-produc-fe870152f634.herokuapp.com/fdc/products?shop=test-hodmedod.myshopify.com + method: get + uri: https://env-0105831.jcloud-ver-jpe.ik-server.com/api/dfc/Enterprises/test-hodmedod/SuppliedProducts body: - encoding: UTF-8 - string: '{"userId":"testdfc@protonmail.com","accessToken":""}' + encoding: US-ASCII + string: '' headers: Content-Type: - application/json @@ -23,33 +23,27 @@ http_interactions: message: Forbidden headers: Server: - - Cowboy - Report-To: - - '{"group":"heroku-nel","max_age":3600,"endpoints":[{"url":"https://nel.heroku.com/reports?ts=1716531220&sid=812dcc77-0bd0-43b1-a5f1-b25750382959&s=GSiP%2FtCyGGyQZrjxJKzy4%2F8ZDbqeNOf8qWTTKv61%2FjQ%3D"}]}' - Reporting-Endpoints: - - heroku-nel=https://nel.heroku.com/reports?ts=1716531220&sid=812dcc77-0bd0-43b1-a5f1-b25750382959&s=GSiP%2FtCyGGyQZrjxJKzy4%2F8ZDbqeNOf8qWTTKv61%2FjQ%3D - Nel: - - '{"report_to":"heroku-nel","max_age":3600,"success_fraction":0.005,"failure_fraction":0.05,"response_headers":["Via"]}' + - openresty + Date: + - Fri, 02 Aug 2024 05:29:39 GMT + Content-Type: + - application/json; charset=utf-8 + Content-Length: + - '78' Connection: - keep-alive X-Powered-By: - Express Access-Control-Allow-Origin: - "*" - Content-Type: - - application/json; charset=utf-8 - Content-Length: - - '62' Etag: - - W/"3e-3yNPCMU4MDQmKmieGPWfDcA/0Eg" - Date: - - Fri, 24 May 2024 06:13:41 GMT - Via: - - 1.1 vegur + - W/"4e-571rdPbjoh7u5Gmg96Iozzasopg" + Strict-Transport-Security: + - max-age=15811200 body: encoding: UTF-8 - string: '{"message":"User access denied","error":"User not authorized"}' - recorded_at: Fri, 24 May 2024 06:13:41 GMT + string: '{"message":"User access denied - token expired","error":"User not authorized"}' + recorded_at: Fri, 02 Aug 2024 05:29:39 GMT - request: method: get uri: https://login.lescommuns.org/auth/realms/data-food-consortium/.well-known/openid-configuration @@ -69,7 +63,7 @@ http_interactions: message: OK headers: Date: - - Fri, 24 May 2024 06:13:42 GMT + - Fri, 02 Aug 2024 05:29:41 GMT Content-Type: - application/json;charset=UTF-8 Transfer-Encoding: @@ -79,14 +73,14 @@ http_interactions: Vary: - Accept-Encoding Set-Cookie: - - AUTH_SESSION_ID=1716531223.827.7041.811327|6055218c9898cae39f8ffd531999e49a; + - AUTH_SESSION_ID=1722576582.635.145555.256088|78230f584c0d7db97d376e98de5321dc; Path=/; Secure; HttpOnly Cache-Control: - no-cache, must-revalidate, no-transform, no-store Referrer-Policy: - no-referrer Strict-Transport-Security: - - max-age=15724800; includeSubDomains + - max-age=31536000; includeSubDomains X-Content-Type-Options: - nosniff X-Frame-Options: @@ -97,7 +91,7 @@ http_interactions: encoding: ASCII-8BIT string: '{"issuer":"https://login.lescommuns.org/auth/realms/data-food-consortium","authorization_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/auth","token_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/token","introspection_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/token/introspect","userinfo_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/userinfo","end_session_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/logout","frontchannel_logout_session_supported":true,"frontchannel_logout_supported":true,"jwks_uri":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/certs","check_session_iframe":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/login-status-iframe.html","grant_types_supported":["authorization_code","implicit","refresh_token","password","client_credentials","urn:openid:params:grant-type:ciba","urn:ietf:params:oauth:grant-type:device_code"],"acr_values_supported":["0","1"],"response_types_supported":["code","none","id_token","token","id_token token","code id_token","code token","code id_token token"],"subject_types_supported":["public","pairwise"],"id_token_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512"],"id_token_encryption_alg_values_supported":["RSA-OAEP","RSA-OAEP-256","RSA1_5"],"id_token_encryption_enc_values_supported":["A256GCM","A192GCM","A128GCM","A128CBC-HS256","A192CBC-HS384","A256CBC-HS512"],"userinfo_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512","none"],"userinfo_encryption_alg_values_supported":["RSA-OAEP","RSA-OAEP-256","RSA1_5"],"userinfo_encryption_enc_values_supported":["A256GCM","A192GCM","A128GCM","A128CBC-HS256","A192CBC-HS384","A256CBC-HS512"],"request_object_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512","none"],"request_object_encryption_alg_values_supported":["RSA-OAEP","RSA-OAEP-256","RSA1_5"],"request_object_encryption_enc_values_supported":["A256GCM","A192GCM","A128GCM","A128CBC-HS256","A192CBC-HS384","A256CBC-HS512"],"response_modes_supported":["query","fragment","form_post","query.jwt","fragment.jwt","form_post.jwt","jwt"],"registration_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/clients-registrations/openid-connect","token_endpoint_auth_methods_supported":["private_key_jwt","client_secret_basic","client_secret_post","tls_client_auth","client_secret_jwt"],"token_endpoint_auth_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512"],"introspection_endpoint_auth_methods_supported":["private_key_jwt","client_secret_basic","client_secret_post","tls_client_auth","client_secret_jwt"],"introspection_endpoint_auth_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512"],"authorization_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512"],"authorization_encryption_alg_values_supported":["RSA-OAEP","RSA-OAEP-256","RSA1_5"],"authorization_encryption_enc_values_supported":["A256GCM","A192GCM","A128GCM","A128CBC-HS256","A192CBC-HS384","A256CBC-HS512"],"claims_supported":["aud","sub","iss","auth_time","name","given_name","family_name","preferred_username","email","acr"],"claim_types_supported":["normal"],"claims_parameter_supported":true,"scopes_supported":["openid","microprofile-jwt","phone","roles","profile","email","address","web-origins","acr","offline_access"],"request_parameter_supported":true,"request_uri_parameter_supported":true,"require_request_uri_registration":true,"code_challenge_methods_supported":["plain","S256"],"tls_client_certificate_bound_access_tokens":true,"revocation_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/revoke","revocation_endpoint_auth_methods_supported":["private_key_jwt","client_secret_basic","client_secret_post","tls_client_auth","client_secret_jwt"],"revocation_endpoint_auth_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512"],"backchannel_logout_supported":true,"backchannel_logout_session_supported":true,"device_authorization_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/auth/device","backchannel_token_delivery_modes_supported":["poll","ping"],"backchannel_authentication_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/ext/ciba/auth","backchannel_authentication_request_signing_alg_values_supported":["PS384","ES384","RS384","ES256","RS256","ES512","PS256","PS512","RS512"],"require_pushed_authorization_requests":false,"pushed_authorization_request_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/ext/par/request","mtls_endpoint_aliases":{"token_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/token","revocation_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/revoke","introspection_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/token/introspect","device_authorization_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/auth/device","registration_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/clients-registrations/openid-connect","userinfo_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/userinfo","pushed_authorization_request_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/ext/par/request","backchannel_authentication_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/ext/ciba/auth"},"authorization_response_iss_parameter_supported":true}' - recorded_at: Fri, 24 May 2024 06:13:43 GMT + recorded_at: Fri, 02 Aug 2024 05:29:41 GMT - request: method: post uri: https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/token @@ -121,7 +115,7 @@ http_interactions: message: OK headers: Date: - - Fri, 24 May 2024 06:13:44 GMT + - Fri, 02 Aug 2024 05:29:42 GMT Content-Type: - application/json Transfer-Encoding: @@ -131,7 +125,7 @@ http_interactions: Vary: - Accept-Encoding Set-Cookie: - - AUTH_SESSION_ID=1716531225.15.7041.192535|6055218c9898cae39f8ffd531999e49a; + - AUTH_SESSION_ID=1722576583.864.9579.316995|78230f584c0d7db97d376e98de5321dc; Path=/; Secure; HttpOnly Cache-Control: - no-store @@ -140,7 +134,7 @@ http_interactions: Referrer-Policy: - no-referrer Strict-Transport-Security: - - max-age=15724800; includeSubDomains + - max-age=31536000; includeSubDomains X-Content-Type-Options: - nosniff X-Frame-Options: @@ -149,15 +143,15 @@ http_interactions: - 1; mode=block body: encoding: ASCII-8BIT - string: '{"access_token":"","expires_in":1800,"refresh_expires_in":31357813,"refresh_token":"","token_type":"Bearer","id_token":"","not-before-policy":0,"session_state":"cfaa4a60-c2aa-4590-9fdf-a117f23d564f","scope":"openid + string: '{"access_token":"","expires_in":1800,"refresh_expires_in":31453394,"refresh_token":"","token_type":"Bearer","id_token":"","not-before-policy":0,"session_state":"207aea32-9912-47cb-b8ad-7508448912b8","scope":"openid profile email"}' - recorded_at: Fri, 24 May 2024 06:13:44 GMT + recorded_at: Fri, 02 Aug 2024 05:29:43 GMT - request: - method: post - uri: https://food-data-collaboration-produc-fe870152f634.herokuapp.com/fdc/products?shop=test-hodmedod.myshopify.com + method: get + uri: https://env-0105831.jcloud-ver-jpe.ik-server.com/api/dfc/Enterprises/test-hodmedod/SuppliedProducts body: - encoding: UTF-8 - string: '{"userId":"testdfc@protonmail.com","accessToken":""}' + encoding: US-ASCII + string: '' headers: Content-Type: - application/json @@ -175,32 +169,30 @@ http_interactions: message: OK headers: Server: - - Cowboy - Report-To: - - '{"group":"heroku-nel","max_age":3600,"endpoints":[{"url":"https://nel.heroku.com/reports?ts=1716531225&sid=812dcc77-0bd0-43b1-a5f1-b25750382959&s=zHpdjRNvPwW4u7pYofDRsdOcjztCveqnM3K9GcGjhMU%3D"}]}' - Reporting-Endpoints: - - heroku-nel=https://nel.heroku.com/reports?ts=1716531225&sid=812dcc77-0bd0-43b1-a5f1-b25750382959&s=zHpdjRNvPwW4u7pYofDRsdOcjztCveqnM3K9GcGjhMU%3D - Nel: - - '{"report_to":"heroku-nel","max_age":3600,"success_fraction":0.005,"failure_fraction":0.05,"response_headers":["Via"]}' + - openresty + Date: + - Fri, 02 Aug 2024 05:29:44 GMT + Content-Type: + - text/html; charset=utf-8 + Content-Length: + - '15329' Connection: - keep-alive X-Powered-By: - Express Access-Control-Allow-Origin: - "*" - Content-Type: - - application/json; charset=utf-8 - Content-Length: - - '41161' Etag: - - W/"a0c9-f4oAeN9fidSaWKNQXG3R8vniAac" - Date: - - Fri, 24 May 2024 06:13:49 GMT - Via: - - 1.1 vegur + - W/"3be1-OcCyKhhY7ZDkbp72mY+FlALOBIo" + Set-Cookie: + - SRVGROUP=common; path=/; HttpOnly + X-Resolver-Ip: + - 185.172.100.59 + Strict-Transport-Security: + - max-age=15811200 body: encoding: ASCII-8BIT string: !binary |- - eyJwcm9kdWN0cyI6IntcIkBjb250ZXh0XCI6XCJodHRwczovL3d3dy5kYXRhZm9vZGNvbnNvcnRpdW0ub3JnXCIsXCJAZ3JhcGhcIjpbe1wiQGlkXCI6XCJfOmIxXCIsXCJAdHlwZVwiOlwiZGZjLWI6UXVhbnRpdGF0aXZlVmFsdWVcIixcImRmYy1iOmhhc1VuaXRcIjpcImRmYy1tOktpbG9ncmFtXCIsXCJkZmMtYjp2YWx1ZVwiOlwiMC40XCJ9LHtcIkBpZFwiOlwiXzpiMTBcIixcIkB0eXBlXCI6XCJkZmMtYjpQcmljZVwiLFwiZGZjLWI6VkFUcmF0ZVwiOlwiMFwiLFwiZGZjLWI6aGFzVW5pdFwiOlwiZGZjLW06RXVyb1wiLFwiZGZjLWI6dmFsdWVcIjpcIjIuODlcIn0se1wiQGlkXCI6XCJfOmIxMVwiLFwiQHR5cGVcIjpcImRmYy1iOlF1YW50aXRhdGl2ZVZhbHVlXCIsXCJkZmMtYjpoYXNVbml0XCI6XCJkZmMtbTpLaWxvZ3JhbVwiLFwiZGZjLWI6dmFsdWVcIjpcIjAuNFwifSx7XCJAaWRcIjpcIl86YjEyXCIsXCJAdHlwZVwiOlwiZGZjLWI6UHJpY2VcIixcImRmYy1iOlZBVHJhdGVcIjpcIjBcIixcImRmYy1iOmhhc1VuaXRcIjpcImRmYy1tOkV1cm9cIixcImRmYy1iOnZhbHVlXCI6XCIwLjk5XCJ9LHtcIkBpZFwiOlwiXzpiMTNcIixcIkB0eXBlXCI6XCJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZVwiLFwiZGZjLWI6aGFzVW5pdFwiOlwiZGZjLW06S2lsb2dyYW1cIixcImRmYy1iOnZhbHVlXCI6XCIwLjNcIn0se1wiQGlkXCI6XCJfOmIxNFwiLFwiQHR5cGVcIjpcImRmYy1iOlByaWNlXCIsXCJkZmMtYjpWQVRyYXRlXCI6XCIwXCIsXCJkZmMtYjpoYXNVbml0XCI6XCJkZmMtbTpFdXJvXCIsXCJkZmMtYjp2YWx1ZVwiOlwiMi45OVwifSx7XCJAaWRcIjpcIl86YjE1XCIsXCJAdHlwZVwiOlwiZGZjLWI6UXVhbnRpdGF0aXZlVmFsdWVcIixcImRmYy1iOmhhc1VuaXRcIjpcImRmYy1tOktpbG9ncmFtXCIsXCJkZmMtYjp2YWx1ZVwiOlwiNC44XCJ9LHtcIkBpZFwiOlwiXzpiMTZcIixcIkB0eXBlXCI6XCJkZmMtYjpQcmljZVwiLFwiZGZjLWI6VkFUcmF0ZVwiOlwiMFwiLFwiZGZjLWI6aGFzVW5pdFwiOlwiZGZjLW06RXVyb1wiLFwiZGZjLWI6dmFsdWVcIjpcIjE4Ljg1XCJ9LHtcIkBpZFwiOlwiXzpiMTdcIixcIkB0eXBlXCI6XCJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZVwiLFwiZGZjLWI6aGFzVW5pdFwiOlwiZGZjLW06S2lsb2dyYW1cIixcImRmYy1iOnZhbHVlXCI6XCI0LjhcIn0se1wiQGlkXCI6XCJfOmIxOFwiLFwiQHR5cGVcIjpcImRmYy1iOlByaWNlXCIsXCJkZmMtYjpWQVRyYXRlXCI6XCIwXCIsXCJkZmMtYjpoYXNVbml0XCI6XCJkZmMtbTpFdXJvXCIsXCJkZmMtYjp2YWx1ZVwiOlwiNy40MlwifSx7XCJAaWRcIjpcIl86YjE5XCIsXCJAdHlwZVwiOlwiZGZjLWI6UXVhbnRpdGF0aXZlVmFsdWVcIixcImRmYy1iOmhhc1VuaXRcIjpcImRmYy1tOktpbG9ncmFtXCIsXCJkZmMtYjp2YWx1ZVwiOlwiNVwifSx7XCJAaWRcIjpcIl86YjJcIixcIkB0eXBlXCI6XCJkZmMtYjpQcmljZVwiLFwiZGZjLWI6VkFUcmF0ZVwiOlwiMFwiLFwiZGZjLWI6aGFzVW5pdFwiOlwiZGZjLW06RXVyb1wiLFwiZGZjLWI6dmFsdWVcIjpcIjIuMDlcIn0se1wiQGlkXCI6XCJfOmIyMFwiLFwiQHR5cGVcIjpcImRmYy1iOlByaWNlXCIsXCJkZmMtYjpWQVRyYXRlXCI6XCIwXCIsXCJkZmMtYjpoYXNVbml0XCI6XCJkZmMtbTpFdXJvXCIsXCJkZmMtYjp2YWx1ZVwiOlwiMTIuNjBcIn0se1wiQGlkXCI6XCJfOmIyMVwiLFwiQHR5cGVcIjpcImRmYy1iOlF1YW50aXRhdGl2ZVZhbHVlXCIsXCJkZmMtYjpoYXNVbml0XCI6XCJkZmMtbTpLaWxvZ3JhbVwiLFwiZGZjLWI6dmFsdWVcIjpcIjQuOFwifSx7XCJAaWRcIjpcIl86YjIyXCIsXCJAdHlwZVwiOlwiZGZjLWI6UHJpY2VcIixcImRmYy1iOlZBVHJhdGVcIjpcIjBcIixcImRmYy1iOmhhc1VuaXRcIjpcImRmYy1tOkV1cm9cIixcImRmYy1iOnZhbHVlXCI6XCI4Ljc2XCJ9LHtcIkBpZFwiOlwiXzpiMjNcIixcIkB0eXBlXCI6XCJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZVwiLFwiZGZjLWI6aGFzVW5pdFwiOlwiZGZjLW06S2lsb2dyYW1cIixcImRmYy1iOnZhbHVlXCI6XCIxLjA1XCJ9LHtcIkBpZFwiOlwiXzpiMjRcIixcIkB0eXBlXCI6XCJkZmMtYjpQcmljZVwiLFwiZGZjLWI6VkFUcmF0ZVwiOlwiMFwiLFwiZGZjLWI6aGFzVW5pdFwiOlwiZGZjLW06RXVyb1wiLFwiZGZjLWI6dmFsdWVcIjpcIjEzLjA1XCJ9LHtcIkBpZFwiOlwiXzpiMjVcIixcIkB0eXBlXCI6XCJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZVwiLFwiZGZjLWI6aGFzVW5pdFwiOlwiZGZjLW06S2lsb2dyYW1cIixcImRmYy1iOnZhbHVlXCI6XCI0LjhcIn0se1wiQGlkXCI6XCJfOmIyNlwiLFwiQHR5cGVcIjpcImRmYy1iOlByaWNlXCIsXCJkZmMtYjpWQVRyYXRlXCI6XCIwXCIsXCJkZmMtYjpoYXNVbml0XCI6XCJkZmMtbTpFdXJvXCIsXCJkZmMtYjp2YWx1ZVwiOlwiNi43NlwifSx7XCJAaWRcIjpcIl86YjI3XCIsXCJAdHlwZVwiOlwiZGZjLWI6UXVhbnRpdGF0aXZlVmFsdWVcIixcImRmYy1iOmhhc1VuaXRcIjpcImRmYy1tOktpbG9ncmFtXCIsXCJkZmMtYjp2YWx1ZVwiOlwiM1wifSx7XCJAaWRcIjpcIl86YjI4XCIsXCJAdHlwZVwiOlwiZGZjLWI6UHJpY2VcIixcImRmYy1iOlZBVHJhdGVcIjpcIjBcIixcImRmYy1iOmhhc1VuaXRcIjpcImRmYy1tOkV1cm9cIixcImRmYy1iOnZhbHVlXCI6XCIxNS45MFwifSx7XCJAaWRcIjpcIl86YjI5XCIsXCJAdHlwZVwiOlwiZGZjLWI6UXVhbnRpdGF0aXZlVmFsdWVcIixcImRmYy1iOmhhc1VuaXRcIjpcImRmYy1tOlBpZWNlXCIsXCJkZmMtYjp2YWx1ZVwiOlwiMTJcIn0se1wiQGlkXCI6XCJfOmIzXCIsXCJAdHlwZVwiOlwiZGZjLWI6UXVhbnRpdGF0aXZlVmFsdWVcIixcImRmYy1iOmhhc1VuaXRcIjpcImRmYy1tOktpbG9ncmFtXCIsXCJkZmMtYjp2YWx1ZVwiOlwiMC40XCJ9LHtcIkBpZFwiOlwiXzpiMzBcIixcIkB0eXBlXCI6XCJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZVwiLFwiZGZjLWI6aGFzVW5pdFwiOlwiZGZjLW06UGllY2VcIixcImRmYy1iOnZhbHVlXCI6XCIxXCJ9LHtcIkBpZFwiOlwiXzpiMzFcIixcIkB0eXBlXCI6XCJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZVwiLFwiZGZjLWI6aGFzVW5pdFwiOlwiZGZjLW06UGllY2VcIixcImRmYy1iOnZhbHVlXCI6XCIxMlwifSx7XCJAaWRcIjpcIl86YjMyXCIsXCJAdHlwZVwiOlwiZGZjLWI6UXVhbnRpdGF0aXZlVmFsdWVcIixcImRmYy1iOmhhc1VuaXRcIjpcImRmYy1tOlBpZWNlXCIsXCJkZmMtYjp2YWx1ZVwiOlwiMVwifSx7XCJAaWRcIjpcIl86YjMzXCIsXCJAdHlwZVwiOlwiZGZjLWI6UXVhbnRpdGF0aXZlVmFsdWVcIixcImRmYy1iOmhhc1VuaXRcIjpcImRmYy1tOlBpZWNlXCIsXCJkZmMtYjp2YWx1ZVwiOlwiMTBcIn0se1wiQGlkXCI6XCJfOmIzNFwiLFwiQHR5cGVcIjpcImRmYy1iOlF1YW50aXRhdGl2ZVZhbHVlXCIsXCJkZmMtYjpoYXNVbml0XCI6XCJkZmMtbTpQaWVjZVwiLFwiZGZjLWI6dmFsdWVcIjpcIjFcIn0se1wiQGlkXCI6XCJfOmIzNVwiLFwiQHR5cGVcIjpcImRmYy1iOlF1YW50aXRhdGl2ZVZhbHVlXCIsXCJkZmMtYjpoYXNVbml0XCI6XCJkZmMtbTpQaWVjZVwiLFwiZGZjLWI6dmFsdWVcIjpcIjEyXCJ9LHtcIkBpZFwiOlwiXzpiMzZcIixcIkB0eXBlXCI6XCJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZVwiLFwiZGZjLWI6aGFzVW5pdFwiOlwiZGZjLW06UGllY2VcIixcImRmYy1iOnZhbHVlXCI6XCIxXCJ9LHtcIkBpZFwiOlwiXzpiMzdcIixcIkB0eXBlXCI6XCJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZVwiLFwiZGZjLWI6aGFzVW5pdFwiOlwiZGZjLW06UGllY2VcIixcImRmYy1iOnZhbHVlXCI6XCI2XCJ9LHtcIkBpZFwiOlwiXzpiMzhcIixcIkB0eXBlXCI6XCJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZVwiLFwiZGZjLWI6aGFzVW5pdFwiOlwiZGZjLW06UGllY2VcIixcImRmYy1iOnZhbHVlXCI6XCIxXCJ9LHtcIkBpZFwiOlwiXzpiMzlcIixcIkB0eXBlXCI6XCJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZVwiLFwiZGZjLWI6aGFzVW5pdFwiOlwiZGZjLW06UGllY2VcIixcImRmYy1iOnZhbHVlXCI6XCIxMlwifSx7XCJAaWRcIjpcIl86YjRcIixcIkB0eXBlXCI6XCJkZmMtYjpQcmljZVwiLFwiZGZjLWI6VkFUcmF0ZVwiOlwiMFwiLFwiZGZjLWI6aGFzVW5pdFwiOlwiZGZjLW06RXVyb1wiLFwiZGZjLWI6dmFsdWVcIjpcIjEuMTlcIn0se1wiQGlkXCI6XCJfOmI0MFwiLFwiQHR5cGVcIjpcImRmYy1iOlF1YW50aXRhdGl2ZVZhbHVlXCIsXCJkZmMtYjpoYXNVbml0XCI6XCJkZmMtbTpQaWVjZVwiLFwiZGZjLWI6dmFsdWVcIjpcIjFcIn0se1wiQGlkXCI6XCJfOmI0MVwiLFwiQHR5cGVcIjpcImRmYy1iOlF1YW50aXRhdGl2ZVZhbHVlXCIsXCJkZmMtYjpoYXNVbml0XCI6XCJkZmMtbTpQaWVjZVwiLFwiZGZjLWI6dmFsdWVcIjpcIjEwXCJ9LHtcIkBpZFwiOlwiXzpiNDJcIixcIkB0eXBlXCI6XCJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZVwiLFwiZGZjLWI6aGFzVW5pdFwiOlwiZGZjLW06UGllY2VcIixcImRmYy1iOnZhbHVlXCI6XCIxXCJ9LHtcIkBpZFwiOlwiXzpiNVwiLFwiQHR5cGVcIjpcImRmYy1iOlF1YW50aXRhdGl2ZVZhbHVlXCIsXCJkZmMtYjpoYXNVbml0XCI6XCJkZmMtbTpLaWxvZ3JhbVwiLFwiZGZjLWI6dmFsdWVcIjpcIjAuNVwifSx7XCJAaWRcIjpcIl86YjZcIixcIkB0eXBlXCI6XCJkZmMtYjpQcmljZVwiLFwiZGZjLWI6VkFUcmF0ZVwiOlwiMFwiLFwiZGZjLWI6aGFzVW5pdFwiOlwiZGZjLW06RXVyb1wiLFwiZGZjLWI6dmFsdWVcIjpcIjEuNjlcIn0se1wiQGlkXCI6XCJfOmI3XCIsXCJAdHlwZVwiOlwiZGZjLWI6UXVhbnRpdGF0aXZlVmFsdWVcIixcImRmYy1iOmhhc1VuaXRcIjpcImRmYy1tOktpbG9ncmFtXCIsXCJkZmMtYjp2YWx1ZVwiOlwiMC40XCJ9LHtcIkBpZFwiOlwiXzpiOFwiLFwiQHR5cGVcIjpcImRmYy1iOlByaWNlXCIsXCJkZmMtYjpWQVRyYXRlXCI6XCIwXCIsXCJkZmMtYjpoYXNVbml0XCI6XCJkZmMtbTpFdXJvXCIsXCJkZmMtYjp2YWx1ZVwiOlwiMS4zOVwifSx7XCJAaWRcIjpcIl86YjlcIixcIkB0eXBlXCI6XCJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZVwiLFwiZGZjLWI6aGFzVW5pdFwiOlwiZGZjLW06S2lsb2dyYW1cIixcImRmYy1iOnZhbHVlXCI6XCIwLjE3NVwifSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MTk0NjY0Njc2MzVcIixcIkB0eXBlXCI6XCJkZmMtYjpTdXBwbGllZFByb2R1Y3RcIixcImRmYy1iOmRlc2NyaXB0aW9uXCI6XCI8dGFibGUgd2lkdGg9XFxcIjEwMCVcXFwiPlxcbjx0Ym9keT5cXG48dHIgc3R5bGU9XFxcImJvcmRlcjogMHB4O1xcXCI+XFxuPHRkIGJnY29sb3I9XFxcIiNkNmZiZWRcXFwiIHN0eWxlPVxcXCJjb2xvcjogIzAwMDAwMDsgYm9yZGVyOiAwcHg7XFxcIj48c3Ryb25nPlRoZXkncmUgYmFjayE8L3N0cm9uZz48L3RkPlxcbjwvdHI+XFxuPC90Ym9keT5cXG48L3RhYmxlPlxcbjxwPjxzdHJvbmc+VGhpbmsgYmFrZWQgYmVhbnMgYXJlIEJyaXRpc2g/IFRoZXkgYXJlIG5vdyEgV2UgdXNlIG9ubHkgQnJpdGlzaC1ncm93biBmYXZhIGJlYW5zIC0gQnJpdGFpbidzIG9yaWdpbmFsIGJlYW4sIGdyb3duIGhlcmUgc2luY2UgdGhlIElyb24gQWdlLiBPdXIgQmFrZWQgQnJpdGlzaCBCZWFucyBhcmUgZGVsaWNpb3VzbHkgZGlmZmVyZW50LCB3aXRoIGxhcmdlIG1lYXR5IGZhdmEgYmVhbnMgaW4gYSB0YXN0eSB0b21hdG8gc2F1Y2UuPC9zdHJvbmc+PC9wPlxcbjxwPjxzdHJvbmc+PGEgdGl0bGU9XFxcIldoYXQgYXJlIGZhdmEgYmVhbnM/IEFyZW4ndCB0aGV5IGp1c3QgYnJvYWQgYmVhbnM/XFxcIiBocmVmPVxcXCIvYmxvZ3MvbmV3cy93aGF0LWFyZS1mYXZhLWJlYW5zLWFyZS10aGV5LWp1c3QtYnJvYWQtYmVhbnNcXFwiIGRhdGEtbWNlLWZyYWdtZW50PVxcXCIxXFxcIiBkYXRhLW1jZS1ocmVmPVxcXCIvYmxvZ3MvbmV3cy93aGF0LWFyZS1mYXZhLWJlYW5zLWFyZS10aGV5LWp1c3QtYnJvYWQtYmVhbnNcXFwiPldoYXQgYXJlIGZhdmEgYmVhbnM/IEZpbmQgb3V0IGhlcmUuLi48L2E+PC9zdHJvbmc+PC9wPlxcbjwhLS0gc3BsaXQgLS0+PGgzPkNvbXBsZXRlIFByb2R1Y3QgRGV0YWlsczwvaDM+PHA+T3VyIEJha2VkIEJyaXRpc2ggQmVhbnMgYXJlIGNvb2tlZCBhbmQgcmVhZHkgdG8gZWF0LCBob3Qgb3IgY29sZC4gVGhleSdyZSBnb29kIHNlcnZlZCBvbiB0b2FzdCBidXQgYWxzbyBkZWxpY2lvdXMgYWRkZWQgdG8gc3Rld3MsIGN1cnJpZXMgb3IgY2Fzc2Vyb2xlcy4gT3IgZXZlbiBpbiBhIHBpZS48L3A+XFxuPGg1IGNsYXNzPVxcXCJwcm9kdWN0LWRldGFpbC10aXRsZVxcXCI+Q29va2luZyBpbnN0cnVjdGlvbnM8L2g1PlxcbjxwPjxzdHJvbmc+Q29va2luZyBvbiB0aGUgSG9iPC9zdHJvbmc+PGJyPkVtcHR5IGNvbnRlbnRzIGludG8gc2F1Y2VwYW4uIEhlYXQgZ2VudGx5IGZvciA0LTUgbWludXRlcyB3aGlsZSBzdGlycmluZy4gRm9yIGJlc3QgZmxhdm91ciBkbyBub3QgYm9pbCBvciBvdmVyY29vay4gRG8gbm90IHJlaGVhdC48L3A+XFxuPHA+PHN0cm9uZz5NaWNyb3dhdmUgQ29va2luZzwvc3Ryb25nPjxicj5FbXB0eSBjb250ZW50cyBpbnRvIGEgbm9uLW1ldGFsbGljIGJvd2wgYW5kIGNvdmVyLiBIZWF0IGZvciAyIHRvIDMgbWludXRlcywgc3RpcnJpbmcgaGFsZndheS4gQ2hlY2sgdGhlIGZvb2QgaXMgaG90LCBzdGlyIHdlbGwgYW5kIHNlcnZlLiBEbyBub3QgcmVoZWF0LjwvcD5cXG48aDUgY2xhc3M9XFxcInByb2R1Y3QtZGV0YWlsLXRpdGxlXFxcIj5UbyBTdG9yZTwvaDU+XFxuPHA+U3RvcmUgaW4gYSBjb29sLCBkcnkgcGxhY2UuIE9uY2Ugb3BlbmVkLCB0cmFuc2ZlciBjb250ZW50cyB0byBhIG5vbi1tZXRhbGxpYyBjb250YWluZXIsIGNvdmVyIHJlZnJpZ2VyYXRlIGFuZCB1c2Ugd2l0aCAyIGRheXMuPC9wPlxcbjxoNSBjbGFzcz1cXFwicHJvZHVjdC1kZXRhaWwtdGl0bGVcXFwiPkluZ3JlZGllbnRzPC9oNT5cXG48cD5GYXZhIEJlYW5zIChCcm9hZCBCZWFucykgKDQyJSksIFdhdGVyLCBUb21hdG8gUHVyZWUsIFN1Z2FyLCBNb2RpZmllZCBNYWl6ZSBTdGFyY2gsIFNhbHQsIEhlcmJzICZhbXA7IFNwaWNlcywgQ29uY2VudHJhdGVkIExlbW9uIEp1aWNlPC9wPlxcbjxoNSBjbGFzcz1cXFwicHJvZHVjdC1kZXRhaWwtdGl0bGVcXFwiPkFsbGVyZ3kgaW5mb3JtYXRpb248L2g1PlxcbjxwPk5vIEFsbGVyZ2VuczwvcD5cXG48dGFibGUgd2lkdGg9XFxcIjEwMCVcXFwiPlxcbjx0Ym9keT5cXG48dHI+XFxuPHRkPjxzdHJvbmc+VHlwaWNhbCB2YWx1ZXM8L3N0cm9uZz48L3RkPlxcbjx0ZD48c3Ryb25nPlBlciAxMDBnPC9zdHJvbmc+PC90ZD5cXG48L3RyPlxcbjx0cj5cXG48dGQ+RW5lcmd5PC90ZD5cXG48dGQ+Mjkya0ogKDY5a2NhbCk8L3RkPlxcbjwvdHI+XFxuPHRyPlxcbjx0ZD5GYXQ8L3RkPlxcbjx0ZD4wLjRnPC90ZD5cXG48L3RyPlxcbjx0cj5cXG48dGQ+b2Ygd2hpY2ggc2F0dXJhdGVzPC90ZD5cXG48dGQ+MC4xZzwvdGQ+XFxuPC90cj5cXG48dHI+XFxuPHRkPkNhcmJvaHlkcmF0ZTwvdGQ+XFxuPHRkPjEwLjFnPC90ZD5cXG48L3RyPlxcbjx0cj5cXG48dGQ+b2Ygd2hpY2ggc3VnYXJzPC90ZD5cXG48dGQ+NC42ZzwvdGQ+XFxuPC90cj5cXG48dHI+XFxuPHRkPkZpYnJlPC90ZD5cXG48dGQ+NWc8L3RkPlxcbjwvdHI+XFxuPHRyPlxcbjx0ZD5Qcm90ZWluPC90ZD5cXG48dGQ+NGc8L3RkPlxcbjwvdHI+XFxuPHRyPlxcbjx0ZD5TYWx0PC90ZD5cXG48dGQ+MC42ZzwvdGQ+XFxuPC90cj5cXG48L3Rib2R5PlxcbjwvdGFibGU+PGg1IGNsYXNzPVxcXCJwcm9kdWN0LWRldGFpbC10aXRsZVxcXCI+TW9yZTwvaDU+XFxuPHA+RGVsaWNpb3VzLCBudXRyaXRpb3VzIGFuZCBnb29kIGZvciB0aGUgc29pbCwgZmF2YSBiZWFucyBhcmUgYSB2YXJpZXR5IG9mIGJyb2FkIGJlYW4sIFZpY2lhIGZhYmEsIGxlZnQgdG8gcmlwZW4gYW5kIGRyeSBiZWZvcmUgaGFydmVzdC4gVGhleeKAmXJlIGFsc28ga25vd24gYXMgZmllbGQgYmVhbnMsIGhvcnNlIGJlYW5zLCBXaW5kc29yIGJlYW5zIG9yIGZ1bC48L3A+XFxuPHA+U3VpdGFibGUgZm9yIHZlZ2FucyBhbmQgdmVnZXRhcmlhbnM8L3A+XFxuXCIsXCJkZmMtYjpoYXNRdWFudGl0eVwiOlwiXzpiMVwiLFwiZGZjLWI6aW1hZ2VcIjpcImh0dHBzOi8vY2RuLnNob3BpZnkuY29tL3MvZmlsZXMvMS8wNzMxLzg0ODMvNzkzOS9wcm9kdWN0cy9QYWNrLUNhbi1CYWtlZC1CZWFucy0xODAweDZfOTgzeDY1Nl81MTM3NThlNi0yNjE2LTQ2ODctYThiMi1iYTZkZGU4NjQ5MjMuanBnP3Y9MTY3Nzc2MDc3OFwiLFwiZGZjLWI6bmFtZVwiOlwiQmFrZWQgQnJpdGlzaCBCZWFucyAtIFJldGFpbCBjYW4sIDQwMGcgKGNhbilcIixcImRmYy1iOnJlZmVyZW5jZWRCeVwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ2NjQ2NzYzNS9jYXRhbG9nSXRlbVwifSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MTk0NjY0Njc2MzUvY2F0YWxvZ0l0ZW1cIixcIkB0eXBlXCI6XCJkZmMtYjpDYXRhbG9nSXRlbVwiLFwiZGZjLWI6b2ZmZXJlZFRocm91Z2hcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MTk0NjY0Njc2MzUvb2ZmZXJcIixcImRmYy1iOnNrdVwiOlwiTkNCQi9UNFwiLFwiZGZjLWI6c3RvY2tMaW1pdGF0aW9uXCI6XCItMVwifSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MTk0NjY0Njc2MzUvb2ZmZXJcIixcIkB0eXBlXCI6XCJkZmMtYjpPZmZlclwiLFwiZGZjLWI6aGFzUHJpY2VcIjp7XCJAaWRcIjpcIl86YjJcIn19LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ2NjQ2NzYzNS9wbGFubmVkQ29uc3VtcHRpb25GbG93XCIsXCJAdHlwZVwiOlwiZGZjLWI6QXNQbGFubmVkQ29uc3VtcHRpb25GbG93XCIsXCJkZmMtYjpjb25zdW1lc1wiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ2NjQ2NzYzNVwiLFwiZGZjLWI6aGFzUXVhbnRpdHlcIjpcIl86YjI5XCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ2NjQ2NzYzNS9wbGFubmVkUHJvZHVjdGlvbkZsb3dcIixcIkB0eXBlXCI6XCJkZmMtYjpBc1BsYW5uZWRQcm9kdWN0aW9uRmxvd1wiLFwiZGZjLWI6aGFzUXVhbnRpdHlcIjpcIl86YjMwXCIsXCJkZmMtYjpwcm9kdWNlc1wiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ2NjUwMDQwM1wifSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MTk0NjY0Njc2MzUvdHJhbnNmb3JtYXRpb25cIixcIkB0eXBlXCI6XCJkZmMtYjpBc1BsYW5uZWRUcmFuc2Zvcm1hdGlvblwiLFwiZGZjLWI6aGFzSW5jb21lXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDY2NDY3NjM1L3BsYW5uZWRDb25zdW1wdGlvbkZsb3dcIixcImRmYy1iOmhhc091dGNvbWVcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MTk0NjY0Njc2MzUvcGxhbm5lZFByb2R1Y3Rpb25GbG93XCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ2NjUwMDQwM1wiLFwiQHR5cGVcIjpcImRmYy1iOlN1cHBsaWVkUHJvZHVjdFwiLFwiZGZjLWI6ZGVzY3JpcHRpb25cIjpcIjx0YWJsZSB3aWR0aD1cXFwiMTAwJVxcXCI+XFxuPHRib2R5Plxcbjx0ciBzdHlsZT1cXFwiYm9yZGVyOiAwcHg7XFxcIj5cXG48dGQgYmdjb2xvcj1cXFwiI2Q2ZmJlZFxcXCIgc3R5bGU9XFxcImNvbG9yOiAjMDAwMDAwOyBib3JkZXI6IDBweDtcXFwiPjxzdHJvbmc+VGhleSdyZSBiYWNrITwvc3Ryb25nPjwvdGQ+XFxuPC90cj5cXG48L3Rib2R5PlxcbjwvdGFibGU+XFxuPHA+PHN0cm9uZz5UaGluayBiYWtlZCBiZWFucyBhcmUgQnJpdGlzaD8gVGhleSBhcmUgbm93ISBXZSB1c2Ugb25seSBCcml0aXNoLWdyb3duIGZhdmEgYmVhbnMgLSBCcml0YWluJ3Mgb3JpZ2luYWwgYmVhbiwgZ3Jvd24gaGVyZSBzaW5jZSB0aGUgSXJvbiBBZ2UuIE91ciBCYWtlZCBCcml0aXNoIEJlYW5zIGFyZSBkZWxpY2lvdXNseSBkaWZmZXJlbnQsIHdpdGggbGFyZ2UgbWVhdHkgZmF2YSBiZWFucyBpbiBhIHRhc3R5IHRvbWF0byBzYXVjZS48L3N0cm9uZz48L3A+XFxuPHA+PHN0cm9uZz48YSB0aXRsZT1cXFwiV2hhdCBhcmUgZmF2YSBiZWFucz8gQXJlbid0IHRoZXkganVzdCBicm9hZCBiZWFucz9cXFwiIGhyZWY9XFxcIi9ibG9ncy9uZXdzL3doYXQtYXJlLWZhdmEtYmVhbnMtYXJlLXRoZXktanVzdC1icm9hZC1iZWFuc1xcXCIgZGF0YS1tY2UtZnJhZ21lbnQ9XFxcIjFcXFwiIGRhdGEtbWNlLWhyZWY9XFxcIi9ibG9ncy9uZXdzL3doYXQtYXJlLWZhdmEtYmVhbnMtYXJlLXRoZXktanVzdC1icm9hZC1iZWFuc1xcXCI+V2hhdCBhcmUgZmF2YSBiZWFucz8gRmluZCBvdXQgaGVyZS4uLjwvYT48L3N0cm9uZz48L3A+XFxuPCEtLSBzcGxpdCAtLT48aDM+Q29tcGxldGUgUHJvZHVjdCBEZXRhaWxzPC9oMz48cD5PdXIgQmFrZWQgQnJpdGlzaCBCZWFucyBhcmUgY29va2VkIGFuZCByZWFkeSB0byBlYXQsIGhvdCBvciBjb2xkLiBUaGV5J3JlIGdvb2Qgc2VydmVkIG9uIHRvYXN0IGJ1dCBhbHNvIGRlbGljaW91cyBhZGRlZCB0byBzdGV3cywgY3VycmllcyBvciBjYXNzZXJvbGVzLiBPciBldmVuIGluIGEgcGllLjwvcD5cXG48aDUgY2xhc3M9XFxcInByb2R1Y3QtZGV0YWlsLXRpdGxlXFxcIj5Db29raW5nIGluc3RydWN0aW9uczwvaDU+XFxuPHA+PHN0cm9uZz5Db29raW5nIG9uIHRoZSBIb2I8L3N0cm9uZz48YnI+RW1wdHkgY29udGVudHMgaW50byBzYXVjZXBhbi4gSGVhdCBnZW50bHkgZm9yIDQtNSBtaW51dGVzIHdoaWxlIHN0aXJyaW5nLiBGb3IgYmVzdCBmbGF2b3VyIGRvIG5vdCBib2lsIG9yIG92ZXJjb29rLiBEbyBub3QgcmVoZWF0LjwvcD5cXG48cD48c3Ryb25nPk1pY3Jvd2F2ZSBDb29raW5nPC9zdHJvbmc+PGJyPkVtcHR5IGNvbnRlbnRzIGludG8gYSBub24tbWV0YWxsaWMgYm93bCBhbmQgY292ZXIuIEhlYXQgZm9yIDIgdG8gMyBtaW51dGVzLCBzdGlycmluZyBoYWxmd2F5LiBDaGVjayB0aGUgZm9vZCBpcyBob3QsIHN0aXIgd2VsbCBhbmQgc2VydmUuIERvIG5vdCByZWhlYXQuPC9wPlxcbjxoNSBjbGFzcz1cXFwicHJvZHVjdC1kZXRhaWwtdGl0bGVcXFwiPlRvIFN0b3JlPC9oNT5cXG48cD5TdG9yZSBpbiBhIGNvb2wsIGRyeSBwbGFjZS4gT25jZSBvcGVuZWQsIHRyYW5zZmVyIGNvbnRlbnRzIHRvIGEgbm9uLW1ldGFsbGljIGNvbnRhaW5lciwgY292ZXIgcmVmcmlnZXJhdGUgYW5kIHVzZSB3aXRoIDIgZGF5cy48L3A+XFxuPGg1IGNsYXNzPVxcXCJwcm9kdWN0LWRldGFpbC10aXRsZVxcXCI+SW5ncmVkaWVudHM8L2g1PlxcbjxwPkZhdmEgQmVhbnMgKEJyb2FkIEJlYW5zKSAoNDIlKSwgV2F0ZXIsIFRvbWF0byBQdXJlZSwgU3VnYXIsIE1vZGlmaWVkIE1haXplIFN0YXJjaCwgU2FsdCwgSGVyYnMgJmFtcDsgU3BpY2VzLCBDb25jZW50cmF0ZWQgTGVtb24gSnVpY2U8L3A+XFxuPGg1IGNsYXNzPVxcXCJwcm9kdWN0LWRldGFpbC10aXRsZVxcXCI+QWxsZXJneSBpbmZvcm1hdGlvbjwvaDU+XFxuPHA+Tm8gQWxsZXJnZW5zPC9wPlxcbjx0YWJsZSB3aWR0aD1cXFwiMTAwJVxcXCI+XFxuPHRib2R5Plxcbjx0cj5cXG48dGQ+PHN0cm9uZz5UeXBpY2FsIHZhbHVlczwvc3Ryb25nPjwvdGQ+XFxuPHRkPjxzdHJvbmc+UGVyIDEwMGc8L3N0cm9uZz48L3RkPlxcbjwvdHI+XFxuPHRyPlxcbjx0ZD5FbmVyZ3k8L3RkPlxcbjx0ZD4yOTJrSiAoNjlrY2FsKTwvdGQ+XFxuPC90cj5cXG48dHI+XFxuPHRkPkZhdDwvdGQ+XFxuPHRkPjAuNGc8L3RkPlxcbjwvdHI+XFxuPHRyPlxcbjx0ZD5vZiB3aGljaCBzYXR1cmF0ZXM8L3RkPlxcbjx0ZD4wLjFnPC90ZD5cXG48L3RyPlxcbjx0cj5cXG48dGQ+Q2FyYm9oeWRyYXRlPC90ZD5cXG48dGQ+MTAuMWc8L3RkPlxcbjwvdHI+XFxuPHRyPlxcbjx0ZD5vZiB3aGljaCBzdWdhcnM8L3RkPlxcbjx0ZD40LjZnPC90ZD5cXG48L3RyPlxcbjx0cj5cXG48dGQ+RmlicmU8L3RkPlxcbjx0ZD41ZzwvdGQ+XFxuPC90cj5cXG48dHI+XFxuPHRkPlByb3RlaW48L3RkPlxcbjx0ZD40ZzwvdGQ+XFxuPC90cj5cXG48dHI+XFxuPHRkPlNhbHQ8L3RkPlxcbjx0ZD4wLjZnPC90ZD5cXG48L3RyPlxcbjwvdGJvZHk+XFxuPC90YWJsZT48aDUgY2xhc3M9XFxcInByb2R1Y3QtZGV0YWlsLXRpdGxlXFxcIj5Nb3JlPC9oNT5cXG48cD5EZWxpY2lvdXMsIG51dHJpdGlvdXMgYW5kIGdvb2QgZm9yIHRoZSBzb2lsLCBmYXZhIGJlYW5zIGFyZSBhIHZhcmlldHkgb2YgYnJvYWQgYmVhbiwgVmljaWEgZmFiYSwgbGVmdCB0byByaXBlbiBhbmQgZHJ5IGJlZm9yZSBoYXJ2ZXN0LiBUaGV54oCZcmUgYWxzbyBrbm93biBhcyBmaWVsZCBiZWFucywgaG9yc2UgYmVhbnMsIFdpbmRzb3IgYmVhbnMgb3IgZnVsLjwvcD5cXG48cD5TdWl0YWJsZSBmb3IgdmVnYW5zIGFuZCB2ZWdldGFyaWFuczwvcD5cXG5cIixcImRmYy1iOmhhc1F1YW50aXR5XCI6XCJfOmIxNVwiLFwiZGZjLWI6aW1hZ2VcIjpcImh0dHBzOi8vY2RuLnNob3BpZnkuY29tL3MvZmlsZXMvMS8wNzMxLzg0ODMvNzkzOS9wcm9kdWN0cy9QYWNrLUNhbi1CYWtlZC1CZWFucy0xODAweDZfOTgzeDY1Nl81MTM3NThlNi0yNjE2LTQ2ODctYThiMi1iYTZkZGU4NjQ5MjMuanBnP3Y9MTY3Nzc2MDc3OFwiLFwiZGZjLWI6bmFtZVwiOlwiQmFrZWQgQnJpdGlzaCBCZWFucyAtIENhc2UsIDEyIHggNDAwZyAoY2FuKVwiLFwiZGZjLWI6cmVmZXJlbmNlZEJ5XCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDY2NTAwNDAzL2NhdGFsb2dJdGVtXCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ2NjUwMDQwMy9jYXRhbG9nSXRlbVwiLFwiQHR5cGVcIjpcImRmYy1iOkNhdGFsb2dJdGVtXCIsXCJkZmMtYjpvZmZlcmVkVGhyb3VnaFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ2NjUwMDQwMy9vZmZlclwiLFwiZGZjLWI6c2t1XCI6XCJOQ0JCL0NEXCIsXCJkZmMtYjpzdG9ja0xpbWl0YXRpb25cIjpcIi0xXCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ2NjUwMDQwMy9vZmZlclwiLFwiQHR5cGVcIjpcImRmYy1iOk9mZmVyXCIsXCJkZmMtYjpoYXNQcmljZVwiOntcIkBpZFwiOlwiXzpiMTZcIn19LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ2NjUzMzE3MVwiLFwiQHR5cGVcIjpcImRmYy1iOlN1cHBsaWVkUHJvZHVjdFwiLFwiZGZjLWI6ZGVzY3JpcHRpb25cIjpcIjx0YWJsZSB3aWR0aD1cXFwiMTAwJVxcXCI+XFxuPHRib2R5Plxcbjx0ciBzdHlsZT1cXFwiYm9yZGVyOiAwcHg7XFxcIj5cXG48dGQgYmdjb2xvcj1cXFwiI2Q2ZmJlZFxcXCIgc3R5bGU9XFxcImNvbG9yOiAjMDAwMDAwOyBib3JkZXI6IDBweDtcXFwiPjxzdHJvbmc+U29ycnksIHN0YW5kYXJkIGJhcmxleSBmbGFrZXMgYXJlIG5vIGxvbmdlciBhdmFpbGFibGUgYnV0IG91ciBkZWxpY2lvdXMgIE9yZ2FuaWMgTmFrZWQgQmFybGV5IEZsYWtlcyBhcmUgYmFjay48L3N0cm9uZz48L3RkPlxcbjwvdHI+XFxuPC90Ym9keT5cXG48L3RhYmxlPjxwPk91ciByaWNoIGFuZCBtYWx0eSBiYXJsZXkgZmxha2VzIGFyZSBhIHN0b3JlIGN1cGJvYXJkIHN0YXBsZS4gT3JnYW5pY2FsbHkgZ3Jvd24gYW5kIG1pbGxlZCBpbiB0aGUgVUssIHRoZXkgYWRkIHRleHR1cmUgdG8gZmxhcGphY2sgYW5kIGJpc2N1aXQgcmVjaXBlcywgb3IgdG8gbWFrZSBhIGhlYXJ0aWVyLCBydXN0aWMgcG9ycmlkZ2Ug4oCTIHRyeSBibGVuZGluZyB3aXRoIG91ciBvdGhlciBmbGFrZXM8L3A+XFxuPCEtLSBzcGxpdCAtLT48aDM+Q29tcGxldGUgUHJvZHVjdCBEZXRhaWxzPC9oMz48bGkgaWQ9XFxcInRhYjFcXFwiIGNsYXNzPVxcXCJhY3RpdmVcXFwiPlxcbjxwPkJhcmxleSBmbGFrZXMgYXJlIGdyZWF0IGFkZGVkIHRvIG11ZXNsaSBvciBncmFub2xhLCBvciB1c2VkIGluIGJha2luZyBhcyBhIHRvcHBpbmcgb3IgbWl4ZWQgaW50byBkb3VnaC4gRWF0IHRoZW0gYXMgYSBjZXJlYWwsIGJha2Ugd2l0aCB0aGVtLCBvciBhZGQgdGhlbSB0byBzb3VwcyBhbmQgc3Rld3MgdG8gdGhpY2tlbiwgYm9vc3QgdGhlaXIgbnV0cml0aW9uIGFuZCBhZGQgZmxhdm91ci48L3A+XFxuPGg1IGNsYXNzPVxcXCJwcm9kdWN0LWRldGFpbC10aXRsZVxcXCI+Q29va2luZyBpbnN0cnVjdGlvbnM8L2g1PlxcbjxwPlRvIGVhdCBhcyBhIG11ZXNsaSwgY29tYmluZSB3aXRoIG90aGVyIGNlcmVhbCBmbGFrZXMgYW5kIGVuam95LiBPciB1c2UgYXMgYW4gb2F0IHN1YnN0aXR1dGUgaW4gYW55IGJha2luZyByZWNpcGUuPC9wPlxcbjxoNSBjbGFzcz1cXFwicHJvZHVjdC1kZXRhaWwtdGl0bGVcXFwiPkluZ3JlZGllbnRzPC9oNT5cXG48cD48Yj5CYXJsZXnCoDwvYj5GbGFrZXM8L3A+XFxuPGg1IGNsYXNzPVxcXCJwcm9kdWN0LWRldGFpbC10aXRsZVxcXCI+QWxsZXJneSBpbmZvcm1hdGlvbjwvaDU+XFxuPHA+Rm9yIGFsbGVyZ2VucywgaW5jbHVkaW5nIGNlcmVhbHMgY29udGFpbmluZyBnbHV0ZW4sIHNlZSBpbmdyZWRpZW50cyBpbiA8c3Ryb25nPmJvbGQ8L3N0cm9uZz48L3A+XFxuPHRhYmxlIHdpZHRoPVxcXCIxMDAlXFxcIj5cXG48dGJvZHk+XFxuPHRyPlxcbjx0ZD48c3Ryb25nPlR5cGljYWwgdmFsdWVzPC9zdHJvbmc+PC90ZD5cXG48dGQ+PHN0cm9uZz5QZXIgMTAwZzwvc3Ryb25nPjwvdGQ+XFxuPC90cj5cXG48dHI+XFxuPHRkPkVuZXJneTwvdGQ+XFxuPHRkPjEsNDAxa0ogKDMzMmtjYWwpPC90ZD5cXG48L3RyPlxcbjx0cj5cXG48dGQ+RmF0PC90ZD5cXG48dGQ+Mi4xZzwvdGQ+XFxuPC90cj5cXG48dHI+XFxuPHRkPm9mIHdoaWNoIHNhdHVyYXRlczwvdGQ+XFxuPHRkPjAuMGc8L3RkPlxcbjwvdHI+XFxuPHRyPlxcbjx0ZD5DYXJib2h5ZHJhdGU8L3RkPlxcbjx0ZD41OC4zZzwvdGQ+XFxuPC90cj5cXG48dHI+XFxuPHRkPm9mIHdoaWNoIHN1Z2FyczwvdGQ+XFxuPHRkPjEuN2c8L3RkPlxcbjwvdHI+XFxuPHRyPlxcbjx0ZD5GaWJyZTwvdGQ+XFxuPHRkPjE3LjNnPC90ZD5cXG48L3RyPlxcbjx0cj5cXG48dGQ+UHJvdGVpbjwvdGQ+XFxuPHRkPjExLjRnPC90ZD5cXG48L3RyPlxcbjx0cj5cXG48dGQ+U2FsdDwvdGQ+XFxuPHRkPjAuMGc8L3RkPlxcbjwvdHI+XFxuPC90Ym9keT5cXG48L3RhYmxlPlxcbjxoNSBjbGFzcz1cXFwicHJvZHVjdC1kZXRhaWwtdGl0bGVcXFwiPk1vcmU8L2g1Plxcbjx1bD5cXG48bGk+U3VpdGFibGUgZm9yIHZlZ2FucyBhbmQgdmVnZXRhcmlhbnNcXG48L2xpPlxcbjxsaT5ObyBhcnRpZmljaWFsIGluZ3JlZGllbnRzXFxuPC9saT5cXG48bGk+R00gZnJlZVxcbjwvbGk+XFxuPGxpPkhpZ2ggRmlicmVcXG48L2xpPlxcbjwvdWw+XFxuPC9saT5cIixcImRmYy1iOmhhc1F1YW50aXR5XCI6XCJfOmI1XCIsXCJkZmMtYjppbWFnZVwiOlwiaHR0cHM6Ly9jZG4uc2hvcGlmeS5jb20vcy9maWxlcy8xLzA3MzEvODQ4My83OTM5L3Byb2R1Y3RzL0JhcmxleS1GbGFrZXMtMjQwMHgxNjAwX2MxMjE0MDdjLTZmZDItNDZjYS1hMTI0LWRiNWRmOTQ0MjM2OC5qcGc/dj0xNjc3NzYwNzgxXCIsXCJkZmMtYjpuYW1lXCI6XCJCYXJsZXkgRmxha2VzLCBPcmdhbmljIC0gUmV0YWlsIHBhY2ssIDUwMGdcIixcImRmYy1iOnJlZmVyZW5jZWRCeVwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ2NjUzMzE3MS9jYXRhbG9nSXRlbVwifSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MTk0NjY1MzMxNzEvY2F0YWxvZ0l0ZW1cIixcIkB0eXBlXCI6XCJkZmMtYjpDYXRhbG9nSXRlbVwiLFwiZGZjLWI6b2ZmZXJlZFRocm91Z2hcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MTk0NjY1MzMxNzEvb2ZmZXJcIixcImRmYy1iOnNrdVwiOlwiT0tCQVI1XCIsXCJkZmMtYjpzdG9ja0xpbWl0YXRpb25cIjpcIi0xXCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ2NjUzMzE3MS9vZmZlclwiLFwiQHR5cGVcIjpcImRmYy1iOk9mZmVyXCIsXCJkZmMtYjpoYXNQcmljZVwiOntcIkBpZFwiOlwiXzpiNlwifX0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDY2NTMzMTcxL3BsYW5uZWRDb25zdW1wdGlvbkZsb3dcIixcIkB0eXBlXCI6XCJkZmMtYjpBc1BsYW5uZWRDb25zdW1wdGlvbkZsb3dcIixcImRmYy1iOmNvbnN1bWVzXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDY2NTMzMTcxXCIsXCJkZmMtYjpoYXNRdWFudGl0eVwiOlwiXzpiMzNcIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDY2NTMzMTcxL3BsYW5uZWRQcm9kdWN0aW9uRmxvd1wiLFwiQHR5cGVcIjpcImRmYy1iOkFzUGxhbm5lZFByb2R1Y3Rpb25GbG93XCIsXCJkZmMtYjpoYXNRdWFudGl0eVwiOlwiXzpiMzRcIixcImRmYy1iOnByb2R1Y2VzXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDY2NTY1OTM5XCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ2NjUzMzE3MS90cmFuc2Zvcm1hdGlvblwiLFwiQHR5cGVcIjpcImRmYy1iOkFzUGxhbm5lZFRyYW5zZm9ybWF0aW9uXCIsXCJkZmMtYjpoYXNJbmNvbWVcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MTk0NjY1MzMxNzEvcGxhbm5lZENvbnN1bXB0aW9uRmxvd1wiLFwiZGZjLWI6aGFzT3V0Y29tZVwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ2NjUzMzE3MS9wbGFubmVkUHJvZHVjdGlvbkZsb3dcIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDY2NTY1OTM5XCIsXCJAdHlwZVwiOlwiZGZjLWI6U3VwcGxpZWRQcm9kdWN0XCIsXCJkZmMtYjpkZXNjcmlwdGlvblwiOlwiPHRhYmxlIHdpZHRoPVxcXCIxMDAlXFxcIj5cXG48dGJvZHk+XFxuPHRyIHN0eWxlPVxcXCJib3JkZXI6IDBweDtcXFwiPlxcbjx0ZCBiZ2NvbG9yPVxcXCIjZDZmYmVkXFxcIiBzdHlsZT1cXFwiY29sb3I6ICMwMDAwMDA7IGJvcmRlcjogMHB4O1xcXCI+PHN0cm9uZz5Tb3JyeSwgc3RhbmRhcmQgYmFybGV5IGZsYWtlcyBhcmUgbm8gbG9uZ2VyIGF2YWlsYWJsZSBidXQgb3VyIGRlbGljaW91cyAgT3JnYW5pYyBOYWtlZCBCYXJsZXkgRmxha2VzIGFyZSBiYWNrLjwvc3Ryb25nPjwvdGQ+XFxuPC90cj5cXG48L3Rib2R5PlxcbjwvdGFibGU+PHA+T3VyIHJpY2ggYW5kIG1hbHR5IGJhcmxleSBmbGFrZXMgYXJlIGEgc3RvcmUgY3VwYm9hcmQgc3RhcGxlLiBPcmdhbmljYWxseSBncm93biBhbmQgbWlsbGVkIGluIHRoZSBVSywgdGhleSBhZGQgdGV4dHVyZSB0byBmbGFwamFjayBhbmQgYmlzY3VpdCByZWNpcGVzLCBvciB0byBtYWtlIGEgaGVhcnRpZXIsIHJ1c3RpYyBwb3JyaWRnZSDigJMgdHJ5IGJsZW5kaW5nIHdpdGggb3VyIG90aGVyIGZsYWtlczwvcD5cXG48IS0tIHNwbGl0IC0tPjxoMz5Db21wbGV0ZSBQcm9kdWN0IERldGFpbHM8L2gzPjxsaSBpZD1cXFwidGFiMVxcXCIgY2xhc3M9XFxcImFjdGl2ZVxcXCI+XFxuPHA+QmFybGV5IGZsYWtlcyBhcmUgZ3JlYXQgYWRkZWQgdG8gbXVlc2xpIG9yIGdyYW5vbGEsIG9yIHVzZWQgaW4gYmFraW5nIGFzIGEgdG9wcGluZyBvciBtaXhlZCBpbnRvIGRvdWdoLiBFYXQgdGhlbSBhcyBhIGNlcmVhbCwgYmFrZSB3aXRoIHRoZW0sIG9yIGFkZCB0aGVtIHRvIHNvdXBzIGFuZCBzdGV3cyB0byB0aGlja2VuLCBib29zdCB0aGVpciBudXRyaXRpb24gYW5kIGFkZCBmbGF2b3VyLjwvcD5cXG48aDUgY2xhc3M9XFxcInByb2R1Y3QtZGV0YWlsLXRpdGxlXFxcIj5Db29raW5nIGluc3RydWN0aW9uczwvaDU+XFxuPHA+VG8gZWF0IGFzIGEgbXVlc2xpLCBjb21iaW5lIHdpdGggb3RoZXIgY2VyZWFsIGZsYWtlcyBhbmQgZW5qb3kuIE9yIHVzZSBhcyBhbiBvYXQgc3Vic3RpdHV0ZSBpbiBhbnkgYmFraW5nIHJlY2lwZS48L3A+XFxuPGg1IGNsYXNzPVxcXCJwcm9kdWN0LWRldGFpbC10aXRsZVxcXCI+SW5ncmVkaWVudHM8L2g1PlxcbjxwPjxiPkJhcmxlecKgPC9iPkZsYWtlczwvcD5cXG48aDUgY2xhc3M9XFxcInByb2R1Y3QtZGV0YWlsLXRpdGxlXFxcIj5BbGxlcmd5IGluZm9ybWF0aW9uPC9oNT5cXG48cD5Gb3IgYWxsZXJnZW5zLCBpbmNsdWRpbmcgY2VyZWFscyBjb250YWluaW5nIGdsdXRlbiwgc2VlIGluZ3JlZGllbnRzIGluIDxzdHJvbmc+Ym9sZDwvc3Ryb25nPjwvcD5cXG48dGFibGUgd2lkdGg9XFxcIjEwMCVcXFwiPlxcbjx0Ym9keT5cXG48dHI+XFxuPHRkPjxzdHJvbmc+VHlwaWNhbCB2YWx1ZXM8L3N0cm9uZz48L3RkPlxcbjx0ZD48c3Ryb25nPlBlciAxMDBnPC9zdHJvbmc+PC90ZD5cXG48L3RyPlxcbjx0cj5cXG48dGQ+RW5lcmd5PC90ZD5cXG48dGQ+MSw0MDFrSiAoMzMya2NhbCk8L3RkPlxcbjwvdHI+XFxuPHRyPlxcbjx0ZD5GYXQ8L3RkPlxcbjx0ZD4yLjFnPC90ZD5cXG48L3RyPlxcbjx0cj5cXG48dGQ+b2Ygd2hpY2ggc2F0dXJhdGVzPC90ZD5cXG48dGQ+MC4wZzwvdGQ+XFxuPC90cj5cXG48dHI+XFxuPHRkPkNhcmJvaHlkcmF0ZTwvdGQ+XFxuPHRkPjU4LjNnPC90ZD5cXG48L3RyPlxcbjx0cj5cXG48dGQ+b2Ygd2hpY2ggc3VnYXJzPC90ZD5cXG48dGQ+MS43ZzwvdGQ+XFxuPC90cj5cXG48dHI+XFxuPHRkPkZpYnJlPC90ZD5cXG48dGQ+MTcuM2c8L3RkPlxcbjwvdHI+XFxuPHRyPlxcbjx0ZD5Qcm90ZWluPC90ZD5cXG48dGQ+MTEuNGc8L3RkPlxcbjwvdHI+XFxuPHRyPlxcbjx0ZD5TYWx0PC90ZD5cXG48dGQ+MC4wZzwvdGQ+XFxuPC90cj5cXG48L3Rib2R5PlxcbjwvdGFibGU+XFxuPGg1IGNsYXNzPVxcXCJwcm9kdWN0LWRldGFpbC10aXRsZVxcXCI+TW9yZTwvaDU+XFxuPHVsPlxcbjxsaT5TdWl0YWJsZSBmb3IgdmVnYW5zIGFuZCB2ZWdldGFyaWFuc1xcbjwvbGk+XFxuPGxpPk5vIGFydGlmaWNpYWwgaW5ncmVkaWVudHNcXG48L2xpPlxcbjxsaT5HTSBmcmVlXFxuPC9saT5cXG48bGk+SGlnaCBGaWJyZVxcbjwvbGk+XFxuPC91bD5cXG48L2xpPlwiLFwiZGZjLWI6aGFzUXVhbnRpdHlcIjpcIl86YjE5XCIsXCJkZmMtYjppbWFnZVwiOlwiaHR0cHM6Ly9jZG4uc2hvcGlmeS5jb20vcy9maWxlcy8xLzA3MzEvODQ4My83OTM5L3Byb2R1Y3RzL0JhcmxleS1GbGFrZXMtMjQwMHgxNjAwX2MxMjE0MDdjLTZmZDItNDZjYS1hMTI0LWRiNWRmOTQ0MjM2OC5qcGc/dj0xNjc3NzYwNzgxXCIsXCJkZmMtYjpuYW1lXCI6XCJCYXJsZXkgRmxha2VzLCBPcmdhbmljIC0gU3RhbmRhcmQgY2FzZSwgMTAgeCA1MDBnXCIsXCJkZmMtYjpyZWZlcmVuY2VkQnlcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MTk0NjY1NjU5MzkvY2F0YWxvZ0l0ZW1cIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDY2NTY1OTM5L2NhdGFsb2dJdGVtXCIsXCJAdHlwZVwiOlwiZGZjLWI6Q2F0YWxvZ0l0ZW1cIixcImRmYy1iOm9mZmVyZWRUaHJvdWdoXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDY2NTY1OTM5L29mZmVyXCIsXCJkZmMtYjpza3VcIjpcIk9LQkFDWFwiLFwiZGZjLWI6c3RvY2tMaW1pdGF0aW9uXCI6XCItMVwifSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MTk0NjY1NjU5Mzkvb2ZmZXJcIixcIkB0eXBlXCI6XCJkZmMtYjpPZmZlclwiLFwiZGZjLWI6aGFzUHJpY2VcIjp7XCJAaWRcIjpcIl86YjIwXCJ9fSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MTk0NzMzNDg5MTVcIixcIkB0eXBlXCI6XCJkZmMtYjpTdXBwbGllZFByb2R1Y3RcIixcImRmYy1iOmRlc2NyaXB0aW9uXCI6XCI8cD48c3Ryb25nPkZlcm1lbnRlZCB3aG9sZWdyYWluIG5ha2VkIGJhcmxleSzCoHRhc3R5IGFuZCBzdWNjdWxlbnTCoGdyYWlucyBvZiByaWNoIG1hbHR5IHVtYW1pIGZsYXZvdXIuPC9zdHJvbmc+PC9wPlxcbjxwPlRoZXNlIHdob2xlIGZlcm1lbnRlZCBiYXJsZXnCoGdyYWlucyBhcmUgcGFja2VkIHdpdGggZGVlcCBmbGF2b3VyIGFuZCBtYWtlIGEgZGVsaWNpb3VzIGFkZGl0aW9uIHRvIGJyZWFkLCByaXNvdHRvLCBzdGV3cywgc2FsYWRzIGFuZCBtb3JlLjwvcD5cXG48IS0tIHNwbGl0IC0tPjxoMz5Db21wbGV0ZSBQcm9kdWN0IERldGFpbHM8L2gzPlxcbjxwPkFkZCBpbnRlbnNlbHkgZmxhdm91cmVkIG1hbHR5IGFuZCBzdWNjdWxlbnQgZ3JhaW5zwqBmdWxsIG9mIHVtYW1pIHJpY2huZXNzIHRvIGJyZWFkcywgcmlzb3R0bywgc3Rld3MsIHNvdXBzLCBhbmQgZXZlbiBzYWxhZHMuPC9wPlxcbjxoNSBjbGFzcz1cXFwicHJvZHVjdC1kZXRhaWwtdGl0bGVcXFwiPkNvb2tpbmcgaW5zdHJ1Y3Rpb25zPC9oNT5cXG48cD5BZGQgYWJvdXQgaGFsZiBhIHRlYXNwb29uIHBlciBzZXJ2aW5nIHRvIGFsbW9zdCBhbnkgZGlzaCBmb3IgYWRkZWQgZGVwdGgsIHVtYW1pIHJpY2huZXNzIGFuZCBtYWx0eSBmbGF2b3VyLjwvcD5cXG48aDUgY2xhc3M9XFxcInByb2R1Y3QtZGV0YWlsLXRpdGxlXFxcIj5UbyBzdG9yZTwvaDU+XFxuPHA+S2VlcCByZWZyaWdlcmF0ZWQgYW5kIHVzZSB3aXRoaW4gNCB3ZWVrcyBvZiBvcGVuaW5nLjwvcD5cXG48aDUgY2xhc3M9XFxcInByb2R1Y3QtZGV0YWlsLXRpdGxlXFxcIj5JbmdyZWRpZW50czwvaDU+XFxuPHA+TmFrZWQgPHN0cm9uZz5CYXJsZXk8L3N0cm9uZz4sIFdhdGVyLCA8c3Ryb25nPldoZWF0PC9zdHJvbmc+IEZsb3VyLCBTYWx0LCBMaXZlIEN1bHR1cmVzKjxicj4gKjxlbT5MYWN0b2JhY2lsbHVzIGRlbGJydWVja2lpPC9lbT4sIDxlbT5Bc3BlcmdpbGx1cyBzb2phZTwvZW0+LCA8ZW0+Wnlnb3NhY2NoYXJvbXljZXMgcm91eGlpPC9lbT48L3A+XFxuPGg1IGNsYXNzPVxcXCJwcm9kdWN0LWRldGFpbC10aXRsZVxcXCI+QWxsZXJneSBpbmZvcm1hdGlvbjwvaDU+XFxuPHA+Q29udGFpbnMgPHN0cm9uZz5CYXJsZXkgKEdsdXRlbik8L3N0cm9uZz4sPHN0cm9uZz4gV2hlYXQgKEdsdXRlbik8L3N0cm9uZz48L3A+XFxuPHRhYmxlIHdpZHRoPVxcXCIxMDAlXFxcIj5cXG48dGJvZHk+XFxuPHRyPlxcbjx0ZD48c3Ryb25nPlR5cGljYWwgdmFsdWVzPC9zdHJvbmc+PC90ZD5cXG48dGQ+PHN0cm9uZz5QZXIgMTAwZzwvc3Ryb25nPjwvdGQ+XFxuPC90cj5cXG48dHI+XFxuPHRkPkVuZXJneTwvdGQ+XFxuPHRkPjUwMGtKICgxMTlrY2FsKTwvdGQ+XFxuPC90cj5cXG48dHI+XFxuPHRkPkZhdDwvdGQ+XFxuPHRkPjEuN2c8L3RkPlxcbjwvdHI+XFxuPHRyPlxcbjx0ZD5vZiB3aGljaCBzYXR1cmF0ZXM8L3RkPlxcbjx0ZD4wLjVnPC90ZD5cXG48L3RyPlxcbjx0cj5cXG48dGQ+Q2FyYm9oeWRyYXRlPC90ZD5cXG48dGQ+MTkuMWc8L3RkPlxcbjwvdHI+XFxuPHRyPlxcbjx0ZD5vZiB3aGljaCBzdWdhcnM8L3RkPlxcbjx0ZD4yLjdnPC90ZD5cXG48L3RyPlxcbjx0cj5cXG48dGQ+RmlicmU8L3RkPlxcbjx0ZD41LjJnPC90ZD5cXG48L3RyPlxcbjx0cj5cXG48dGQ+UHJvdGVpbjwvdGQ+XFxuPHRkPjQuMmc8L3RkPlxcbjwvdHI+XFxuPHRyPlxcbjx0ZD5TYWx0PC90ZD5cXG48dGQ+OC41ZzwvdGQ+XFxuPC90cj5cXG48L3Rib2R5PlxcbjwvdGFibGU+PGg1IGNsYXNzPVxcXCJwcm9kdWN0LWRldGFpbC10aXRsZVxcXCI+TW9yZTwvaDU+XFxuPHA+T3VyIEZlcm1lbnRlZCBXaG9sZWdyYWluIE5ha2VkIEJhcmxleSBpcyBmZXJtZW50ZWQgaW4gdGhlIHNhbWUgd2F5IGFzIG1hbnkgc295YSBmZXJtZW50cyB1c2VkIGZvciBibGFjayBiZWFucywgYnV0IHVzaW5nwqBuYWtlZCBiYXJsZXkgZ3JhaW4gaW5zdGVhZCBvZiBzb3kgYmVhbnMuIEl0J3MgbWFkZSB3aXRoIGp1c3QgbmFrZWQgYmFybGV5IGdyYWlucywgd2F0ZXIgYW5kIHNhbHQsIGZlcm1lbnRlZCB3aXRoIGEgbGl2ZSBjdWx0dXJlIG9mIDxlbT5MYWN0b2JhY2lsbHVzIGRlbGJydWVja2lpPC9lbT4sIDxlbT5Bc3BlcmdpbGx1cyBzb2phZTwvZW0+IGFuZCA8ZW0+Wnlnb3NhY2NoYXJvbXljZXMgcm91eGlpPC9lbT4uPC9wPlxcbjxwPlN1aXRhYmxlIGZvciB2ZWdhbnMuPC9wPlxcbjxwPlBhY2tlZCBpbiByZWN5Y2xhYmxlIGdsYXNzIGphciB3aXRoIG1ldGFsIGxpZC48L3A+XCIsXCJkZmMtYjpoYXNRdWFudGl0eVwiOlwiXzpiOVwiLFwiZGZjLWI6aW1hZ2VcIjpcImh0dHBzOi8vY2RuLnNob3BpZnkuY29tL3MvZmlsZXMvMS8wNzMxLzg0ODMvNzkzOS9wcm9kdWN0cy9GZXJtZW50ZWQtV2hvbGVncmFpbi1OYWtlZC1CYXJsZXktU3Bvb24tMTYwMHgxMDAwX2Q2ZmVhMDkyLWZkZTQtNGE5OC1iZWM4LWJiM2NhMGExZmQ0ZC5qcGc/dj0xNjc3NzYwODYwXCIsXCJkZmMtYjpuYW1lXCI6XCJGZXJtZW50ZWQgTmFrZWQgQmFybGV5IC0gUmV0YWlsIGphciwgMTc1ZyAoamFyKVwiLFwiZGZjLWI6cmVmZXJlbmNlZEJ5XCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDczMzQ4OTE1L2NhdGFsb2dJdGVtXCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ3MzM0ODkxNS9jYXRhbG9nSXRlbVwiLFwiQHR5cGVcIjpcImRmYy1iOkNhdGFsb2dJdGVtXCIsXCJkZmMtYjpvZmZlcmVkVGhyb3VnaFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ3MzM0ODkxNS9vZmZlclwiLFwiZGZjLWI6c2t1XCI6XCJOTU5CL0pGXCIsXCJkZmMtYjpzdG9ja0xpbWl0YXRpb25cIjpcIi0xXCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ3MzM0ODkxNS9vZmZlclwiLFwiQHR5cGVcIjpcImRmYy1iOk9mZmVyXCIsXCJkZmMtYjpoYXNQcmljZVwiOntcIkBpZFwiOlwiXzpiMTBcIn19LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ3MzM0ODkxNS9wbGFubmVkQ29uc3VtcHRpb25GbG93XCIsXCJAdHlwZVwiOlwiZGZjLWI6QXNQbGFubmVkQ29uc3VtcHRpb25GbG93XCIsXCJkZmMtYjpjb25zdW1lc1wiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ3MzM0ODkxNVwiLFwiZGZjLWI6aGFzUXVhbnRpdHlcIjpcIl86YjM3XCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ3MzM0ODkxNS9wbGFubmVkUHJvZHVjdGlvbkZsb3dcIixcIkB0eXBlXCI6XCJkZmMtYjpBc1BsYW5uZWRQcm9kdWN0aW9uRmxvd1wiLFwiZGZjLWI6aGFzUXVhbnRpdHlcIjpcIl86YjM4XCIsXCJkZmMtYjpwcm9kdWNlc1wiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ3MzM4MTY4M1wifSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MTk0NzMzNDg5MTUvdHJhbnNmb3JtYXRpb25cIixcIkB0eXBlXCI6XCJkZmMtYjpBc1BsYW5uZWRUcmFuc2Zvcm1hdGlvblwiLFwiZGZjLWI6aGFzSW5jb21lXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDczMzQ4OTE1L3BsYW5uZWRDb25zdW1wdGlvbkZsb3dcIixcImRmYy1iOmhhc091dGNvbWVcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MTk0NzMzNDg5MTUvcGxhbm5lZFByb2R1Y3Rpb25GbG93XCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ3MzM4MTY4M1wiLFwiQHR5cGVcIjpcImRmYy1iOlN1cHBsaWVkUHJvZHVjdFwiLFwiZGZjLWI6ZGVzY3JpcHRpb25cIjpcIjxwPjxzdHJvbmc+RmVybWVudGVkIHdob2xlZ3JhaW4gbmFrZWQgYmFybGV5LMKgdGFzdHkgYW5kIHN1Y2N1bGVudMKgZ3JhaW5zIG9mIHJpY2ggbWFsdHkgdW1hbWkgZmxhdm91ci48L3N0cm9uZz48L3A+XFxuPHA+VGhlc2Ugd2hvbGUgZmVybWVudGVkIGJhcmxlecKgZ3JhaW5zIGFyZSBwYWNrZWQgd2l0aCBkZWVwIGZsYXZvdXIgYW5kIG1ha2UgYSBkZWxpY2lvdXMgYWRkaXRpb24gdG8gYnJlYWQsIHJpc290dG8sIHN0ZXdzLCBzYWxhZHMgYW5kIG1vcmUuPC9wPlxcbjwhLS0gc3BsaXQgLS0+PGgzPkNvbXBsZXRlIFByb2R1Y3QgRGV0YWlsczwvaDM+XFxuPHA+QWRkIGludGVuc2VseSBmbGF2b3VyZWQgbWFsdHkgYW5kIHN1Y2N1bGVudCBncmFpbnPCoGZ1bGwgb2YgdW1hbWkgcmljaG5lc3MgdG8gYnJlYWRzLCByaXNvdHRvLCBzdGV3cywgc291cHMsIGFuZCBldmVuIHNhbGFkcy48L3A+XFxuPGg1IGNsYXNzPVxcXCJwcm9kdWN0LWRldGFpbC10aXRsZVxcXCI+Q29va2luZyBpbnN0cnVjdGlvbnM8L2g1PlxcbjxwPkFkZCBhYm91dCBoYWxmIGEgdGVhc3Bvb24gcGVyIHNlcnZpbmcgdG8gYWxtb3N0IGFueSBkaXNoIGZvciBhZGRlZCBkZXB0aCwgdW1hbWkgcmljaG5lc3MgYW5kIG1hbHR5IGZsYXZvdXIuPC9wPlxcbjxoNSBjbGFzcz1cXFwicHJvZHVjdC1kZXRhaWwtdGl0bGVcXFwiPlRvIHN0b3JlPC9oNT5cXG48cD5LZWVwIHJlZnJpZ2VyYXRlZCBhbmQgdXNlIHdpdGhpbiA0IHdlZWtzIG9mIG9wZW5pbmcuPC9wPlxcbjxoNSBjbGFzcz1cXFwicHJvZHVjdC1kZXRhaWwtdGl0bGVcXFwiPkluZ3JlZGllbnRzPC9oNT5cXG48cD5OYWtlZCA8c3Ryb25nPkJhcmxleTwvc3Ryb25nPiwgV2F0ZXIsIDxzdHJvbmc+V2hlYXQ8L3N0cm9uZz4gRmxvdXIsIFNhbHQsIExpdmUgQ3VsdHVyZXMqPGJyPiAqPGVtPkxhY3RvYmFjaWxsdXMgZGVsYnJ1ZWNraWk8L2VtPiwgPGVtPkFzcGVyZ2lsbHVzIHNvamFlPC9lbT4sIDxlbT5aeWdvc2FjY2hhcm9teWNlcyByb3V4aWk8L2VtPjwvcD5cXG48aDUgY2xhc3M9XFxcInByb2R1Y3QtZGV0YWlsLXRpdGxlXFxcIj5BbGxlcmd5IGluZm9ybWF0aW9uPC9oNT5cXG48cD5Db250YWlucyA8c3Ryb25nPkJhcmxleSAoR2x1dGVuKTwvc3Ryb25nPiw8c3Ryb25nPiBXaGVhdCAoR2x1dGVuKTwvc3Ryb25nPjwvcD5cXG48dGFibGUgd2lkdGg9XFxcIjEwMCVcXFwiPlxcbjx0Ym9keT5cXG48dHI+XFxuPHRkPjxzdHJvbmc+VHlwaWNhbCB2YWx1ZXM8L3N0cm9uZz48L3RkPlxcbjx0ZD48c3Ryb25nPlBlciAxMDBnPC9zdHJvbmc+PC90ZD5cXG48L3RyPlxcbjx0cj5cXG48dGQ+RW5lcmd5PC90ZD5cXG48dGQ+NTAwa0ogKDExOWtjYWwpPC90ZD5cXG48L3RyPlxcbjx0cj5cXG48dGQ+RmF0PC90ZD5cXG48dGQ+MS43ZzwvdGQ+XFxuPC90cj5cXG48dHI+XFxuPHRkPm9mIHdoaWNoIHNhdHVyYXRlczwvdGQ+XFxuPHRkPjAuNWc8L3RkPlxcbjwvdHI+XFxuPHRyPlxcbjx0ZD5DYXJib2h5ZHJhdGU8L3RkPlxcbjx0ZD4xOS4xZzwvdGQ+XFxuPC90cj5cXG48dHI+XFxuPHRkPm9mIHdoaWNoIHN1Z2FyczwvdGQ+XFxuPHRkPjIuN2c8L3RkPlxcbjwvdHI+XFxuPHRyPlxcbjx0ZD5GaWJyZTwvdGQ+XFxuPHRkPjUuMmc8L3RkPlxcbjwvdHI+XFxuPHRyPlxcbjx0ZD5Qcm90ZWluPC90ZD5cXG48dGQ+NC4yZzwvdGQ+XFxuPC90cj5cXG48dHI+XFxuPHRkPlNhbHQ8L3RkPlxcbjx0ZD44LjVnPC90ZD5cXG48L3RyPlxcbjwvdGJvZHk+XFxuPC90YWJsZT48aDUgY2xhc3M9XFxcInByb2R1Y3QtZGV0YWlsLXRpdGxlXFxcIj5Nb3JlPC9oNT5cXG48cD5PdXIgRmVybWVudGVkIFdob2xlZ3JhaW4gTmFrZWQgQmFybGV5IGlzIGZlcm1lbnRlZCBpbiB0aGUgc2FtZSB3YXkgYXMgbWFueSBzb3lhIGZlcm1lbnRzIHVzZWQgZm9yIGJsYWNrIGJlYW5zLCBidXQgdXNpbmfCoG5ha2VkIGJhcmxleSBncmFpbiBpbnN0ZWFkIG9mIHNveSBiZWFucy4gSXQncyBtYWRlIHdpdGgganVzdCBuYWtlZCBiYXJsZXkgZ3JhaW5zLCB3YXRlciBhbmQgc2FsdCwgZmVybWVudGVkIHdpdGggYSBsaXZlIGN1bHR1cmUgb2YgPGVtPkxhY3RvYmFjaWxsdXMgZGVsYnJ1ZWNraWk8L2VtPiwgPGVtPkFzcGVyZ2lsbHVzIHNvamFlPC9lbT4gYW5kIDxlbT5aeWdvc2FjY2hhcm9teWNlcyByb3V4aWk8L2VtPi48L3A+XFxuPHA+U3VpdGFibGUgZm9yIHZlZ2Fucy48L3A+XFxuPHA+UGFja2VkIGluIHJlY3ljbGFibGUgZ2xhc3MgamFyIHdpdGggbWV0YWwgbGlkLjwvcD5cIixcImRmYy1iOmhhc1F1YW50aXR5XCI6XCJfOmIyM1wiLFwiZGZjLWI6aW1hZ2VcIjpcImh0dHBzOi8vY2RuLnNob3BpZnkuY29tL3MvZmlsZXMvMS8wNzMxLzg0ODMvNzkzOS9wcm9kdWN0cy9GZXJtZW50ZWQtV2hvbGVncmFpbi1OYWtlZC1CYXJsZXktU3Bvb24tMTYwMHgxMDAwX2Q2ZmVhMDkyLWZkZTQtNGE5OC1iZWM4LWJiM2NhMGExZmQ0ZC5qcGc/dj0xNjc3NzYwODYwXCIsXCJkZmMtYjpuYW1lXCI6XCJGZXJtZW50ZWQgTmFrZWQgQmFybGV5IC0gQ2FzZSwgNiB4IDE3NWcgKGphcilcIixcImRmYy1iOnJlZmVyZW5jZWRCeVwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ3MzM4MTY4My9jYXRhbG9nSXRlbVwifSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MTk0NzMzODE2ODMvY2F0YWxvZ0l0ZW1cIixcIkB0eXBlXCI6XCJkZmMtYjpDYXRhbG9nSXRlbVwiLFwiZGZjLWI6b2ZmZXJlZFRocm91Z2hcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MTk0NzMzODE2ODMvb2ZmZXJcIixcImRmYy1iOnNrdVwiOlwiTk1OQi9DNlwiLFwiZGZjLWI6c3RvY2tMaW1pdGF0aW9uXCI6XCItMVwifSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MTk0NzMzODE2ODMvb2ZmZXJcIixcIkB0eXBlXCI6XCJkZmMtYjpPZmZlclwiLFwiZGZjLWI6aGFzUHJpY2VcIjp7XCJAaWRcIjpcIl86YjI0XCJ9fSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2MjcyNDQ4NTFcIixcIkB0eXBlXCI6XCJkZmMtYjpTdXBwbGllZFByb2R1Y3RcIixcImRmYy1iOmhhc1F1YW50aXR5XCI6XCJfOmIxN1wiLFwiZGZjLWI6aW1hZ2VcIjpcImh0dHBzOi8vY2RuLnNob3BpZnkuY29tL3MvZmlsZXMvMS8wNzMxLzg0ODMvNzkzOS9wcm9kdWN0cy9QYWNrLUNhbi1CYWtlZC1CZWFucy0xODAweDZfYTRkNTg0NTktYmY1Mi00OGE5LWJhZTctODA3ZjQwMzViODdmLmpwZz92PTE2Nzc3NjA3NzdcIixcImRmYy1iOm5hbWVcIjpcIkJha2VkIEJyaXRpc2ggQmVhbnMgKFRvTCkgLSBDYXNlIC0gMTIgeCA0MDBnIGNhbnNcIixcImRmYy1iOnJlZmVyZW5jZWRCeVwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTYyNzI0NDg1MS9jYXRhbG9nSXRlbVwifSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2MjcyNDQ4NTEvY2F0YWxvZ0l0ZW1cIixcIkB0eXBlXCI6XCJkZmMtYjpDYXRhbG9nSXRlbVwiLFwiZGZjLWI6b2ZmZXJlZFRocm91Z2hcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2MjcyNDQ4NTEvb2ZmZXJcIixcImRmYy1iOnNrdVwiOlwiTkNCQkNEXCIsXCJkZmMtYjpzdG9ja0xpbWl0YXRpb25cIjpcIi0xXCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTYyNzI0NDg1MS9vZmZlclwiLFwiQHR5cGVcIjpcImRmYy1iOk9mZmVyXCIsXCJkZmMtYjpoYXNQcmljZVwiOntcIkBpZFwiOlwiXzpiMThcIn19LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTYyNzI3NzYxOVwiLFwiQHR5cGVcIjpcImRmYy1iOlN1cHBsaWVkUHJvZHVjdFwiLFwiZGZjLWI6aGFzUXVhbnRpdHlcIjpcIl86YjNcIixcImRmYy1iOmltYWdlXCI6XCJodHRwczovL2Nkbi5zaG9waWZ5LmNvbS9zL2ZpbGVzLzEvMDczMS84NDgzLzc5MzkvcHJvZHVjdHMvUGFjay1DYW4tQmFrZWQtQmVhbnMtMTgwMHg2X2E0ZDU4NDU5LWJmNTItNDhhOS1iYWU3LTgwN2Y0MDM1Yjg3Zi5qcGc/dj0xNjc3NzYwNzc3XCIsXCJkZmMtYjpuYW1lXCI6XCJCYWtlZCBCcml0aXNoIEJlYW5zIChUb0wpIC0gU2luZ2xlIC0gNDAwZyBjYW5cIixcImRmYy1iOnJlZmVyZW5jZWRCeVwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTYyNzI3NzYxOS9jYXRhbG9nSXRlbVwifSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2MjcyNzc2MTkvY2F0YWxvZ0l0ZW1cIixcIkB0eXBlXCI6XCJkZmMtYjpDYXRhbG9nSXRlbVwiLFwiZGZjLWI6b2ZmZXJlZFRocm91Z2hcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2MjcyNzc2MTkvb2ZmZXJcIixcImRmYy1iOnNrdVwiOlwiTkNCQlQ0XCIsXCJkZmMtYjpzdG9ja0xpbWl0YXRpb25cIjpcIjIwXCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTYyNzI3NzYxOS9vZmZlclwiLFwiQHR5cGVcIjpcImRmYy1iOk9mZmVyXCIsXCJkZmMtYjpoYXNQcmljZVwiOntcIkBpZFwiOlwiXzpiNFwifX0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjI3Mjc3NjE5L3BsYW5uZWRDb25zdW1wdGlvbkZsb3dcIixcIkB0eXBlXCI6XCJkZmMtYjpBc1BsYW5uZWRDb25zdW1wdGlvbkZsb3dcIixcImRmYy1iOmNvbnN1bWVzXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjI3Mjc3NjE5XCIsXCJkZmMtYjpoYXNRdWFudGl0eVwiOlwiXzpiMzFcIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjI3Mjc3NjE5L3BsYW5uZWRQcm9kdWN0aW9uRmxvd1wiLFwiQHR5cGVcIjpcImRmYy1iOkFzUGxhbm5lZFByb2R1Y3Rpb25GbG93XCIsXCJkZmMtYjpoYXNRdWFudGl0eVwiOlwiXzpiMzJcIixcImRmYy1iOnByb2R1Y2VzXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjI3MjQ0ODUxXCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTYyNzI3NzYxOS90cmFuc2Zvcm1hdGlvblwiLFwiQHR5cGVcIjpcImRmYy1iOkFzUGxhbm5lZFRyYW5zZm9ybWF0aW9uXCIsXCJkZmMtYjpoYXNJbmNvbWVcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2MjcyNzc2MTkvcGxhbm5lZENvbnN1bXB0aW9uRmxvd1wiLFwiZGZjLWI6aGFzT3V0Y29tZVwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTYyNzI3NzYxOS9wbGFubmVkUHJvZHVjdGlvbkZsb3dcIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjI4Nzg0OTQ3XCIsXCJAdHlwZVwiOlwiZGZjLWI6U3VwcGxpZWRQcm9kdWN0XCIsXCJkZmMtYjpoYXNRdWFudGl0eVwiOlwiXzpiMjVcIixcImRmYy1iOmltYWdlXCI6XCJodHRwczovL2Nkbi5zaG9waWZ5LmNvbS9zL2ZpbGVzLzEvMDczMS84NDgzLzc5MzkvcHJvZHVjdHMvV2hvbGUtRmF2YS1CZWFucy1PcmdhbmljLUNhbm5lZF9mY2I2NGZkNy04Y2EzLTQ2NWEtOGY1Ni00NDNjZjI4ZTBiNzEuanBnP3Y9MTY3Nzc2MDk3N1wiLFwiZGZjLWI6bmFtZVwiOlwiT3JnYW5pYyBXaG9sZSBGYXZhIEJlYW5zIGluIFdhdGVyIChUb0wpIC0gQ2FzZSAtIDEyIHggNDAwZyBjYW5zXCIsXCJkZmMtYjpyZWZlcmVuY2VkQnlcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2Mjg3ODQ5NDcvY2F0YWxvZ0l0ZW1cIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjI4Nzg0OTQ3L2NhdGFsb2dJdGVtXCIsXCJAdHlwZVwiOlwiZGZjLWI6Q2F0YWxvZ0l0ZW1cIixcImRmYy1iOm9mZmVyZWRUaHJvdWdoXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjI4Nzg0OTQ3L29mZmVyXCIsXCJkZmMtYjpza3VcIjpcIk9DRkJDRFwiLFwiZGZjLWI6c3RvY2tMaW1pdGF0aW9uXCI6XCItMVwifSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2Mjg3ODQ5NDcvb2ZmZXJcIixcIkB0eXBlXCI6XCJkZmMtYjpPZmZlclwiLFwiZGZjLWI6aGFzUHJpY2VcIjp7XCJAaWRcIjpcIl86YjI2XCJ9fSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2Mjg4MTc3MTVcIixcIkB0eXBlXCI6XCJkZmMtYjpTdXBwbGllZFByb2R1Y3RcIixcImRmYy1iOmhhc1F1YW50aXR5XCI6XCJfOmIxMVwiLFwiZGZjLWI6aW1hZ2VcIjpcImh0dHBzOi8vY2RuLnNob3BpZnkuY29tL3MvZmlsZXMvMS8wNzMxLzg0ODMvNzkzOS9wcm9kdWN0cy9XaG9sZS1GYXZhLUJlYW5zLU9yZ2FuaWMtQ2FubmVkX2ZjYjY0ZmQ3LThjYTMtNDY1YS04ZjU2LTQ0M2NmMjhlMGI3MS5qcGc/dj0xNjc3NzYwOTc3XCIsXCJkZmMtYjpuYW1lXCI6XCJPcmdhbmljIFdob2xlIEZhdmEgQmVhbnMgaW4gV2F0ZXIgKFRvTCkgLSBTaW5nbGUgLSA0MDBnIGNhblwiLFwiZGZjLWI6cmVmZXJlbmNlZEJ5XCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjI4ODE3NzE1L2NhdGFsb2dJdGVtXCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTYyODgxNzcxNS9jYXRhbG9nSXRlbVwiLFwiQHR5cGVcIjpcImRmYy1iOkNhdGFsb2dJdGVtXCIsXCJkZmMtYjpvZmZlcmVkVGhyb3VnaFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTYyODgxNzcxNS9vZmZlclwiLFwiZGZjLWI6c2t1XCI6XCJPQ0ZCVDRcIixcImRmYy1iOnN0b2NrTGltaXRhdGlvblwiOlwiLTFcIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjI4ODE3NzE1L29mZmVyXCIsXCJAdHlwZVwiOlwiZGZjLWI6T2ZmZXJcIixcImRmYy1iOmhhc1ByaWNlXCI6e1wiQGlkXCI6XCJfOmIxMlwifX0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjI4ODE3NzE1L3BsYW5uZWRDb25zdW1wdGlvbkZsb3dcIixcIkB0eXBlXCI6XCJkZmMtYjpBc1BsYW5uZWRDb25zdW1wdGlvbkZsb3dcIixcImRmYy1iOmNvbnN1bWVzXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjI4ODE3NzE1XCIsXCJkZmMtYjpoYXNRdWFudGl0eVwiOlwiXzpiMzlcIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjI4ODE3NzE1L3BsYW5uZWRQcm9kdWN0aW9uRmxvd1wiLFwiQHR5cGVcIjpcImRmYy1iOkFzUGxhbm5lZFByb2R1Y3Rpb25GbG93XCIsXCJkZmMtYjpoYXNRdWFudGl0eVwiOlwiXzpiNDBcIixcImRmYy1iOnByb2R1Y2VzXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjI4Nzg0OTQ3XCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTYyODgxNzcxNS90cmFuc2Zvcm1hdGlvblwiLFwiQHR5cGVcIjpcImRmYy1iOkFzUGxhbm5lZFRyYW5zZm9ybWF0aW9uXCIsXCJkZmMtYjpoYXNJbmNvbWVcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2Mjg4MTc3MTUvcGxhbm5lZENvbnN1bXB0aW9uRmxvd1wiLFwiZGZjLWI6aGFzT3V0Y29tZVwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTYyODgxNzcxNS9wbGFubmVkUHJvZHVjdGlvbkZsb3dcIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjU0MDQ5MDc1XCIsXCJAdHlwZVwiOlwiZGZjLWI6U3VwcGxpZWRQcm9kdWN0XCIsXCJkZmMtYjpoYXNRdWFudGl0eVwiOlwiXzpiN1wiLFwiZGZjLWI6bmFtZVwiOlwiQ2FybGluIFBlYXMgaW4gV2F0ZXIsIE9yZ2FuaWMgKERJU1RSSUJVVE9SKSAtIFJldGFpbCBjYW4gKDQwMGcgY2FuKVwiLFwiZGZjLWI6cmVmZXJlbmNlZEJ5XCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjU0MDQ5MDc1L2NhdGFsb2dJdGVtXCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTY1NDA0OTA3NS9jYXRhbG9nSXRlbVwiLFwiQHR5cGVcIjpcImRmYy1iOkNhdGFsb2dJdGVtXCIsXCJkZmMtYjpvZmZlcmVkVGhyb3VnaFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTY1NDA0OTA3NS9vZmZlclwiLFwiZGZjLWI6c2t1XCI6XCJPQ0NQVDRcIixcImRmYy1iOnN0b2NrTGltaXRhdGlvblwiOlwiLTFcIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjU0MDQ5MDc1L29mZmVyXCIsXCJAdHlwZVwiOlwiZGZjLWI6T2ZmZXJcIixcImRmYy1iOmhhc1ByaWNlXCI6e1wiQGlkXCI6XCJfOmI4XCJ9fSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2NTQwNDkwNzUvcGxhbm5lZENvbnN1bXB0aW9uRmxvd1wiLFwiQHR5cGVcIjpcImRmYy1iOkFzUGxhbm5lZENvbnN1bXB0aW9uRmxvd1wiLFwiZGZjLWI6Y29uc3VtZXNcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2NTQwNDkwNzVcIixcImRmYy1iOmhhc1F1YW50aXR5XCI6XCJfOmIzNVwifSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2NTQwNDkwNzUvcGxhbm5lZFByb2R1Y3Rpb25GbG93XCIsXCJAdHlwZVwiOlwiZGZjLWI6QXNQbGFubmVkUHJvZHVjdGlvbkZsb3dcIixcImRmYy1iOmhhc1F1YW50aXR5XCI6XCJfOmIzNlwiLFwiZGZjLWI6cHJvZHVjZXNcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2NTQwODE4NDNcIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjU0MDQ5MDc1L3RyYW5zZm9ybWF0aW9uXCIsXCJAdHlwZVwiOlwiZGZjLWI6QXNQbGFubmVkVHJhbnNmb3JtYXRpb25cIixcImRmYy1iOmhhc0luY29tZVwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTY1NDA0OTA3NS9wbGFubmVkQ29uc3VtcHRpb25GbG93XCIsXCJkZmMtYjpoYXNPdXRjb21lXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjU0MDQ5MDc1L3BsYW5uZWRQcm9kdWN0aW9uRmxvd1wifSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2NTQwODE4NDNcIixcIkB0eXBlXCI6XCJkZmMtYjpTdXBwbGllZFByb2R1Y3RcIixcImRmYy1iOmhhc1F1YW50aXR5XCI6XCJfOmIyMVwiLFwiZGZjLWI6bmFtZVwiOlwiQ2FybGluIFBlYXMgaW4gV2F0ZXIsIE9yZ2FuaWMgKERJU1RSSUJVVE9SKSAtIFN0YW5kYXJkIGNhc2UgKDEyIHggNDAwZyBjYW4pXCIsXCJkZmMtYjpyZWZlcmVuY2VkQnlcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2NTQwODE4NDMvY2F0YWxvZ0l0ZW1cIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjU0MDgxODQzL2NhdGFsb2dJdGVtXCIsXCJAdHlwZVwiOlwiZGZjLWI6Q2F0YWxvZ0l0ZW1cIixcImRmYy1iOm9mZmVyZWRUaHJvdWdoXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjU0MDgxODQzL29mZmVyXCIsXCJkZmMtYjpza3VcIjpcIk9DQ1BDRFwiLFwiZGZjLWI6c3RvY2tMaW1pdGF0aW9uXCI6XCItMVwifSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2NTQwODE4NDMvb2ZmZXJcIixcIkB0eXBlXCI6XCJkZmMtYjpPZmZlclwiLFwiZGZjLWI6aGFzUHJpY2VcIjp7XCJAaWRcIjpcIl86YjIyXCJ9fSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2NjM1ODQ1NjNcIixcIkB0eXBlXCI6XCJkZmMtYjpTdXBwbGllZFByb2R1Y3RcIixcImRmYy1iOmhhc1F1YW50aXR5XCI6XCJfOmIxM1wiLFwiZGZjLWI6bmFtZVwiOlwiUm9hc3RlZCBGYXZhIEJlYW5zLCBMaWdodGx5IFNlYSBTYWx0ZWQgKERJU1RSSUJVVE9SKSAtIFJldGFpbCBwYWNrICgzMDBnKVwiLFwiZGZjLWI6cmVmZXJlbmNlZEJ5XCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjYzNTg0NTYzL2NhdGFsb2dJdGVtXCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTY2MzU4NDU2My9jYXRhbG9nSXRlbVwiLFwiQHR5cGVcIjpcImRmYy1iOkNhdGFsb2dJdGVtXCIsXCJkZmMtYjpvZmZlcmVkVGhyb3VnaFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTY2MzU4NDU2My9vZmZlclwiLFwiZGZjLWI6c2t1XCI6XCJOUkZTUjNcIixcImRmYy1iOnN0b2NrTGltaXRhdGlvblwiOlwiLTFcIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjYzNTg0NTYzL29mZmVyXCIsXCJAdHlwZVwiOlwiZGZjLWI6T2ZmZXJcIixcImRmYy1iOmhhc1ByaWNlXCI6e1wiQGlkXCI6XCJfOmIxNFwifX0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjYzNTg0NTYzL3BsYW5uZWRDb25zdW1wdGlvbkZsb3dcIixcIkB0eXBlXCI6XCJkZmMtYjpBc1BsYW5uZWRDb25zdW1wdGlvbkZsb3dcIixcImRmYy1iOmNvbnN1bWVzXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjYzNTg0NTYzXCIsXCJkZmMtYjpoYXNRdWFudGl0eVwiOlwiXzpiNDFcIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjYzNTg0NTYzL3BsYW5uZWRQcm9kdWN0aW9uRmxvd1wiLFwiQHR5cGVcIjpcImRmYy1iOkFzUGxhbm5lZFByb2R1Y3Rpb25GbG93XCIsXCJkZmMtYjpoYXNRdWFudGl0eVwiOlwiXzpiNDJcIixcImRmYy1iOnByb2R1Y2VzXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjYzNjE3MzMxXCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTY2MzU4NDU2My90cmFuc2Zvcm1hdGlvblwiLFwiQHR5cGVcIjpcImRmYy1iOkFzUGxhbm5lZFRyYW5zZm9ybWF0aW9uXCIsXCJkZmMtYjpoYXNJbmNvbWVcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2NjM1ODQ1NjMvcGxhbm5lZENvbnN1bXB0aW9uRmxvd1wiLFwiZGZjLWI6aGFzT3V0Y29tZVwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTY2MzU4NDU2My9wbGFubmVkUHJvZHVjdGlvbkZsb3dcIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjYzNjE3MzMxXCIsXCJAdHlwZVwiOlwiZGZjLWI6U3VwcGxpZWRQcm9kdWN0XCIsXCJkZmMtYjpoYXNRdWFudGl0eVwiOlwiXzpiMjdcIixcImRmYy1iOm5hbWVcIjpcIlJvYXN0ZWQgRmF2YSBCZWFucywgTGlnaHRseSBTZWEgU2FsdGVkIChESVNUUklCVVRPUikgLSBTdGFuZGFyZCBjYXNlICgxMCB4IDMwMGcpXCIsXCJkZmMtYjpyZWZlcmVuY2VkQnlcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2NjM2MTczMzEvY2F0YWxvZ0l0ZW1cIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjYzNjE3MzMxL2NhdGFsb2dJdGVtXCIsXCJAdHlwZVwiOlwiZGZjLWI6Q2F0YWxvZ0l0ZW1cIixcImRmYy1iOm9mZmVyZWRUaHJvdWdoXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjYzNjE3MzMxL29mZmVyXCIsXCJkZmMtYjpza3VcIjpcIk5SRlNDWFwiLFwiZGZjLWI6c3RvY2tMaW1pdGF0aW9uXCI6XCItMVwifSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2NjM2MTczMzEvb2ZmZXJcIixcIkB0eXBlXCI6XCJkZmMtYjpPZmZlclwiLFwiZGZjLWI6aGFzUHJpY2VcIjp7XCJAaWRcIjpcIl86YjI4XCJ9fV19IiwibGFzdElkIjoiODE0NzI5MjI1ODYxMSIsInJlbWFpbmluZ1Byb2R1Y3RzQ291bnRBZnRlciI6MCwic3VjY2VzcyI6dHJ1ZSwibWVzc2FnZSI6IlByb2R1Y3RzIHJldHJpZXZlZCBzdWNjZXNzZnVsbHkifQ== - recorded_at: Fri, 24 May 2024 06:13:50 GMT + eyJAY29udGV4dCI6Imh0dHBzOi8vd3d3LmRhdGFmb29kY29uc29ydGl1bS5vcmciLCJAZ3JhcGgiOlt7IkBpZCI6Il86YjEyNSIsIkB0eXBlIjoiZGZjLWI6UXVhbnRpdGF0aXZlVmFsdWUiLCJkZmMtYjpoYXNVbml0IjoiZGZjLW06S2lsb2dyYW0iLCJkZmMtYjp2YWx1ZSI6IjAuMDQifSx7IkBpZCI6Il86YjEyNiIsIkB0eXBlIjoiZGZjLWI6UHJpY2UiLCJkZmMtYjpWQVRyYXRlIjoiMCIsImRmYy1iOmhhc1VuaXQiOiJkZmMtbTpFdXJvIiwiZGZjLWI6dmFsdWUiOiI2LjI1In0seyJAaWQiOiJfOmIxMjciLCJAdHlwZSI6ImRmYy1iOlF1YW50aXRhdGl2ZVZhbHVlIiwiZGZjLWI6aGFzVW5pdCI6ImRmYy1tOktpbG9ncmFtIiwiZGZjLWI6dmFsdWUiOiIwLjI1In0seyJAaWQiOiJfOmIxMjgiLCJAdHlwZSI6ImRmYy1iOlByaWNlIiwiZGZjLWI6VkFUcmF0ZSI6IjAiLCJkZmMtYjpoYXNVbml0IjoiZGZjLW06RXVybyIsImRmYy1iOnZhbHVlIjoiNy4wMCJ9LHsiQGlkIjoiXzpiMTI5IiwiQHR5cGUiOiJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZSIsImRmYy1iOmhhc1VuaXQiOiJkZmMtbTpLaWxvZ3JhbSIsImRmYy1iOnZhbHVlIjoiMC4yNCJ9LHsiQGlkIjoiXzpiMTMwIiwiQHR5cGUiOiJkZmMtYjpQcmljZSIsImRmYy1iOlZBVHJhdGUiOiIwIiwiZGZjLWI6aGFzVW5pdCI6ImRmYy1tOkV1cm8iLCJkZmMtYjp2YWx1ZSI6IjMwLjIwIn0seyJAaWQiOiJfOmIxMzEiLCJAdHlwZSI6ImRmYy1iOlF1YW50aXRhdGl2ZVZhbHVlIiwiZGZjLWI6aGFzVW5pdCI6ImRmYy1tOktpbG9ncmFtIiwiZGZjLWI6dmFsdWUiOiIxLjUifSx7IkBpZCI6Il86YjEzMiIsIkB0eXBlIjoiZGZjLWI6UHJpY2UiLCJkZmMtYjpWQVRyYXRlIjoiMCIsImRmYy1iOmhhc1VuaXQiOiJkZmMtbTpFdXJvIiwiZGZjLWI6dmFsdWUiOiI3OS4wMCJ9LHsiQGlkIjoiXzpiMTMzIiwiQHR5cGUiOiJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZSIsImRmYy1iOmhhc1VuaXQiOiJkZmMtbTpQaWVjZSIsImRmYy1iOnZhbHVlIjoiNiJ9LHsiQGlkIjoiXzpiMTM0IiwiQHR5cGUiOiJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZSIsImRmYy1iOmhhc1VuaXQiOiJkZmMtbTpQaWVjZSIsImRmYy1iOnZhbHVlIjoiMSJ9LHsiQGlkIjoiXzpiMTM1IiwiQHR5cGUiOiJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZSIsImRmYy1iOmhhc1VuaXQiOiJkZmMtbTpQaWVjZSIsImRmYy1iOnZhbHVlIjoiNiJ9LHsiQGlkIjoiXzpiMTM2IiwiQHR5cGUiOiJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZSIsImRmYy1iOmhhc1VuaXQiOiJkZmMtbTpQaWVjZSIsImRmYy1iOnZhbHVlIjoiMSJ9LHsiQGlkIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2MjM4MjU5IiwiQHR5cGUiOiJkZmMtYjpTdXBwbGllZFByb2R1Y3QiLCJkZmMtYjpkZXNjcmlwdGlvbiI6Ijx0YWJsZSB3aWR0aD1cIjEwMCVcIj5cbjx0Ym9keT5cbjx0ciBzdHlsZT1cImJvcmRlcjogMHB4O1wiIGRhdGEtbWNlLXN0eWxlPVwiYm9yZGVyOiAwcHg7XCI+XG48dGQgYmdjb2xvcj1cIiNkNmZiZWRcIiBzdHlsZT1cImNvbG9yOiAjMDAwMDAwOyBib3JkZXI6IDBweDsgd2lkdGg6IDUyNnB4O1wiIGRhdGEtbWNlLXN0eWxlPVwiY29sb3I6ICMwMDAwMDA7IGJvcmRlcjogMHB4OyB3aWR0aDogNTI2cHg7XCI+PGI+VGhpcyByaWNoLCBpbnRlbnNlIGFuZCBkZWVwbHkgZmxhdm91cmVkIDYteWVhciBvbGQgYXBwbGUgYmFsc2FtaWMgdmluZWdhciBpcyBtYWRlIHVzaW5nIHRoZSB0cmFkaXRpb25hbCBJdGFsaWFuIG1ldGhvZCBvZiByZWR1Y3Rpb24gYW5kIGNvbmNlbnRyYXRpb24gb2YgdGhlIGp1aWNlIG92ZXIgYSBsZW5ndGh5IHBlcmlvZCBvZiB0aW1lLCByYXRoZXIgdGhhbiBieSBhZGRpbmcgZmxhdm91cmluZyBhbmQgY29sb3VyaW5nLsKgPC9iPjwvdGQ+XG48L3RyPlxuPC90Ym9keT5cbjwvdGFibGU+XG48cD5MaWJlcnR5IEZpZWxkcyBwcm9kdWNlIHNtYWxsIGJhdGNoZXMgb2Ygc3VwZXJiIHN5cnVwLCBiYWxzYW1pYyB2aW5lZ2FyLCBjaWRlciBhbmQgdm9ka2EgYnkgaGFuZCBmcm9tIHRoZSBmcnVpdCBvZiB0aGVpciBvd24gRG9yc2V0IGFwcGxlIG9yY2hhcmRzLCBwbGFudGVkIGZyb20gMjAxMC48YnI+PC9wPlxuPHA+VGhlIGJhbHNhbWljIHZpbmVnYXIgaXMgYWdlZCBmb3IgNiB5ZWFycyBpbiBiYXJyZWxzLsKgVGhlIG9ubHkgaW5ncmVkaWVudCBpcyBhcHBsZXMuPC9wPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5Ib3cgdG8gdXNlPC9oNT5cbjxwPlVzZSBsaWtlIEl0YWxpYW4gYmFsc2FtaWMgdmluZWdhci7CoDxzcGFuIGRhdGEtbWNlLWZyYWdtZW50PVwiMVwiPkFzIHdlbGwgYXMgdXNpbmcgb24gc2FsYWRzLCBpdOKAmXMgYSBncmVhdCBwYXJ0bmVyIGZvciBncmlsbGVkIG1lYXRzIG9yIGNoYXJjdXRlcmllOyBhIGRyb3AgYnJpbmdzIG91dCB0aGUgdGFzdGUgb2Ygc3RyYXdiZXJyaWVzIGFuZCBvdGhlciBzb2Z0IGZydWl0czsgYW5kIGl0IGNhbiByZWFsbHkgZW5oYW5jZSBhIHN0ZXcsIHNhdWNlIG9yIGEgc291cC7CoDwvc3Bhbj48L3A+XG48aDUgY2xhc3M9XCJwcm9kdWN0LWRldGFpbC10aXRsZVwiPlRvIHN0b3JlPGJyPlxuPC9oNT5cbjxwPkZvciBiZXN0IGJlZm9yZSBkYXRlIHNlZSBwYWNrLiBTdG9yZSBpbiBhIGNvb2wsIGRyeSBwbGFjZS48YnI+PC9wPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5JbmdyZWRpZW50czwvaDU+XG48cD5BcHBsZXM8YnI+PC9wPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5BbGxlcmd5IGluZm9ybWF0aW9uPC9oNT5cbjxwPk5vIGFsbGVyZ2Vucy48L3A+PGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5Nb3JlPC9oNT5cbjxwPlByb2R1Y3Qgb2bCoERvcnNldDxicj5TdWl0YWJsZSBmb3IgdmVnYW5zIGFuZCB2ZWdldGFyaWFuczxicj48L3A+IiwiZGZjLWI6aGFzUXVhbnRpdHkiOiJfOmIxMjUiLCJkZmMtYjppbWFnZSI6Imh0dHBzOi8vY2RuLnNob3BpZnkuY29tL3MvZmlsZXMvMS8wNzMxLzg0ODMvNzkzOS9wcm9kdWN0cy9MaWJlcnR5LUZpZWxkcy1BcHBsZS1CYWxzYW1pYy1WaW5lZ2FyLTQwbWxfNzk2MTdlZWEtYWI4Yy00MDcwLTllNGQtNzExYmYwMzBhZDA3LmpwZz92PTE2Nzc3NjA3NzIiLCJkZmMtYjpuYW1lIjoiQXBwbGUgQmFsc2FtaWMgVmluZWdhciAtIFJldGFpbCBib3R0bGUsIDQwbWwiLCJkZmMtYjpyZWZlcmVuY2VkQnkiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjYyMzgyNTkvQ2F0YWxvZ0l0ZW0ifSx7IkBpZCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjIzODI1OS9Bc1BsYW5uZWRDb25zdW1wdGlvbkZsb3ciLCJAdHlwZSI6ImRmYy1iOkFzUGxhbm5lZENvbnN1bXB0aW9uRmxvdyIsImRmYy1iOmNvbnN1bWVzIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2MjM4MjU5IiwiZGZjLWI6aGFzUXVhbnRpdHkiOiJfOmIxMzMifSx7IkBpZCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjIzODI1OS9Bc1BsYW5uZWRQcm9kdWN0aW9uRmxvdyIsIkB0eXBlIjoiZGZjLWI6QXNQbGFubmVkUHJvZHVjdGlvbkZsb3ciLCJkZmMtYjpoYXNRdWFudGl0eSI6Il86YjEzNCIsImRmYy1iOnByb2R1Y2VzIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2MjcxMDI3In0seyJAaWQiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjYyMzgyNTkvQXNQbGFubmVkVHJhbnNmb3JtYXRpb24iLCJAdHlwZSI6ImRmYy1iOkFzUGxhbm5lZFRyYW5zZm9ybWF0aW9uIiwiZGZjLWI6aGFzSW5jb21lIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2MjM4MjU5L0FzUGxhbm5lZENvbnN1bXB0aW9uRmxvdyIsImRmYy1iOmhhc091dGNvbWUiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjYyMzgyNTkvQXNQbGFubmVkUHJvZHVjdGlvbkZsb3cifSx7IkBpZCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjIzODI1OS9DYXRhbG9nSXRlbSIsIkB0eXBlIjoiZGZjLWI6Q2F0YWxvZ0l0ZW0iLCJkZmMtYjpvZmZlcmVkVGhyb3VnaCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjIzODI1OS9PZmZlciIsImRmYy1iOnNrdSI6IkxJQi9OQUJWSS9CRiIsImRmYy1iOnN0b2NrTGltaXRhdGlvbiI6Ii0xIn0seyJAaWQiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjYyMzgyNTkvT2ZmZXIiLCJAdHlwZSI6ImRmYy1iOk9mZmVyIiwiZGZjLWI6aGFzUHJpY2UiOnsiQGlkIjoiXzpiMTI2In19LHsiQGlkIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2MjcxMDI3IiwiQHR5cGUiOiJkZmMtYjpTdXBwbGllZFByb2R1Y3QiLCJkZmMtYjpkZXNjcmlwdGlvbiI6Ijx0YWJsZSB3aWR0aD1cIjEwMCVcIj5cbjx0Ym9keT5cbjx0ciBzdHlsZT1cImJvcmRlcjogMHB4O1wiIGRhdGEtbWNlLXN0eWxlPVwiYm9yZGVyOiAwcHg7XCI+XG48dGQgYmdjb2xvcj1cIiNkNmZiZWRcIiBzdHlsZT1cImNvbG9yOiAjMDAwMDAwOyBib3JkZXI6IDBweDsgd2lkdGg6IDUyNnB4O1wiIGRhdGEtbWNlLXN0eWxlPVwiY29sb3I6ICMwMDAwMDA7IGJvcmRlcjogMHB4OyB3aWR0aDogNTI2cHg7XCI+PGI+VGhpcyByaWNoLCBpbnRlbnNlIGFuZCBkZWVwbHkgZmxhdm91cmVkIDYteWVhciBvbGQgYXBwbGUgYmFsc2FtaWMgdmluZWdhciBpcyBtYWRlIHVzaW5nIHRoZSB0cmFkaXRpb25hbCBJdGFsaWFuIG1ldGhvZCBvZiByZWR1Y3Rpb24gYW5kIGNvbmNlbnRyYXRpb24gb2YgdGhlIGp1aWNlIG92ZXIgYSBsZW5ndGh5IHBlcmlvZCBvZiB0aW1lLCByYXRoZXIgdGhhbiBieSBhZGRpbmcgZmxhdm91cmluZyBhbmQgY29sb3VyaW5nLsKgPC9iPjwvdGQ+XG48L3RyPlxuPC90Ym9keT5cbjwvdGFibGU+XG48cD5MaWJlcnR5IEZpZWxkcyBwcm9kdWNlIHNtYWxsIGJhdGNoZXMgb2Ygc3VwZXJiIHN5cnVwLCBiYWxzYW1pYyB2aW5lZ2FyLCBjaWRlciBhbmQgdm9ka2EgYnkgaGFuZCBmcm9tIHRoZSBmcnVpdCBvZiB0aGVpciBvd24gRG9yc2V0IGFwcGxlIG9yY2hhcmRzLCBwbGFudGVkIGZyb20gMjAxMC48YnI+PC9wPlxuPHA+VGhlIGJhbHNhbWljIHZpbmVnYXIgaXMgYWdlZCBmb3IgNiB5ZWFycyBpbiBiYXJyZWxzLsKgVGhlIG9ubHkgaW5ncmVkaWVudCBpcyBhcHBsZXMuPC9wPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5Ib3cgdG8gdXNlPC9oNT5cbjxwPlVzZSBsaWtlIEl0YWxpYW4gYmFsc2FtaWMgdmluZWdhci7CoDxzcGFuIGRhdGEtbWNlLWZyYWdtZW50PVwiMVwiPkFzIHdlbGwgYXMgdXNpbmcgb24gc2FsYWRzLCBpdOKAmXMgYSBncmVhdCBwYXJ0bmVyIGZvciBncmlsbGVkIG1lYXRzIG9yIGNoYXJjdXRlcmllOyBhIGRyb3AgYnJpbmdzIG91dCB0aGUgdGFzdGUgb2Ygc3RyYXdiZXJyaWVzIGFuZCBvdGhlciBzb2Z0IGZydWl0czsgYW5kIGl0IGNhbiByZWFsbHkgZW5oYW5jZSBhIHN0ZXcsIHNhdWNlIG9yIGEgc291cC7CoDwvc3Bhbj48L3A+XG48aDUgY2xhc3M9XCJwcm9kdWN0LWRldGFpbC10aXRsZVwiPlRvIHN0b3JlPGJyPlxuPC9oNT5cbjxwPkZvciBiZXN0IGJlZm9yZSBkYXRlIHNlZSBwYWNrLiBTdG9yZSBpbiBhIGNvb2wsIGRyeSBwbGFjZS48YnI+PC9wPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5JbmdyZWRpZW50czwvaDU+XG48cD5BcHBsZXM8YnI+PC9wPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5BbGxlcmd5IGluZm9ybWF0aW9uPC9oNT5cbjxwPk5vIGFsbGVyZ2Vucy48L3A+PGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5Nb3JlPC9oNT5cbjxwPlByb2R1Y3Qgb2bCoERvcnNldDxicj5TdWl0YWJsZSBmb3IgdmVnYW5zIGFuZCB2ZWdldGFyaWFuczxicj48L3A+IiwiZGZjLWI6aGFzUXVhbnRpdHkiOiJfOmIxMjkiLCJkZmMtYjppbWFnZSI6Imh0dHBzOi8vY2RuLnNob3BpZnkuY29tL3MvZmlsZXMvMS8wNzMxLzg0ODMvNzkzOS9wcm9kdWN0cy9MaWJlcnR5LUZpZWxkcy1BcHBsZS1CYWxzYW1pYy1WaW5lZ2FyLTQwbWxfNzk2MTdlZWEtYWI4Yy00MDcwLTllNGQtNzExYmYwMzBhZDA3LmpwZz92PTE2Nzc3NjA3NzIiLCJkZmMtYjpuYW1lIjoiQXBwbGUgQmFsc2FtaWMgVmluZWdhciAtIENhc2UsIDYgeCA0MG1sIiwiZGZjLWI6cmVmZXJlbmNlZEJ5IjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2MjcxMDI3L0NhdGFsb2dJdGVtIn0seyJAaWQiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjYyNzEwMjcvQ2F0YWxvZ0l0ZW0iLCJAdHlwZSI6ImRmYy1iOkNhdGFsb2dJdGVtIiwiZGZjLWI6b2ZmZXJlZFRocm91Z2giOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjYyNzEwMjcvT2ZmZXIiLCJkZmMtYjpza3UiOiJMSUIvTkFCVkkvQzYiLCJkZmMtYjpzdG9ja0xpbWl0YXRpb24iOiI1NSJ9LHsiQGlkIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2MjcxMDI3L09mZmVyIiwiQHR5cGUiOiJkZmMtYjpPZmZlciIsImRmYy1iOmhhc1ByaWNlIjp7IkBpZCI6Il86YjEzMCJ9fSx7IkBpZCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjM2OTMzMSIsIkB0eXBlIjoiZGZjLWI6U3VwcGxpZWRQcm9kdWN0IiwiZGZjLWI6ZGVzY3JpcHRpb24iOiI8dGFibGUgd2lkdGg9XCIxMDAlXCI+XG48dGJvZHk+XG48dHIgc3R5bGU9XCJib3JkZXI6IDBweDtcIiBkYXRhLW1jZS1zdHlsZT1cImJvcmRlcjogMHB4O1wiPlxuPHRkIHN0eWxlPVwiY29sb3I6ICMwMDAwMDA7IGJvcmRlcjogMHB4OyB3aWR0aDogNTI2cHg7XCIgYmdjb2xvcj1cIiNkNmZiZWRcIiBkYXRhLW1jZS1zdHlsZT1cImNvbG9yOiAjMDAwMDAwOyBib3JkZXI6IDBweDsgd2lkdGg6IDUyNnB4O1wiPjxiPkRvcnNldCdzIGFuc3dlciB0byBtYXBsZSBzeXJ1cCwgTGliZXJ0eSBGaWVsZHMnIEFwcGxlIFN5cnVwIGlzIGEgbHV4dXJpb3VzbHkgcmljaCBhbmQgaW50ZW5zZSBuYXR1cmFsIHN3ZWV0ZW5lciwgbWFkZSBvbmx5IGZyb20gdGhlIGFwcGxlcyBvZiB0aGVpciBvcmNoYXJkcy48L2I+PC90ZD5cbjwvdHI+XG48L3Rib2R5PlxuPC90YWJsZT5cbjxwPkxpYmVydHkgRmllbGRzIHByb2R1Y2Ugc21hbGwgYmF0Y2hlcyBvZiBzdXBlcmIgc3lydXAsIGJhbHNhbWljIHZpbmVnYXIsIGNpZGVyIGFuZCB2b2RrYSBieSBoYW5kIGZyb20gdGhlIGZydWl0IG9mIHRoZWlyIG93biBEb3JzZXQgYXBwbGUgb3JjaGFyZHMsIHBsYW50ZWQgZnJvbSAyMDEwLjxicj48L3A+XG48cD5UaGUgc3lydXAgY29udGFpbnMgbm8gYWRkaXRpdmVzLCBjaGVtaWNhbHMsIGZpbmluZyBhZ2VudHMgb3Igc3VnYXIuIE92ZXIgMmtnIGFwcGxlcyBnbyBpbnRvIGVhY2ggYm90dGxlLjwvcD5cbjxsaSBpZD1cInRhYjFcIiBjbGFzcz1cImFjdGl2ZVwiPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5Ib3cgdG8gdXNlPC9oNT5cbjxwPlVzZSBhcyBhIHN3ZWV0ZW5lciwgbGlrZSBtYXBsZSBzeXJ1cC7CoERlbGljaW91cyB3aXRoIHBhbmNha2VzLCB5b2dodXJ0IHBvcnJpZGdlLCBpY2UgY3JlYW0sIEZyZW5jaCB0b2FzdCBhbmQgbW9yZS4gTWl4IHdpdGggc3BhcmtsaW5nIHdhdGVyIG9yIGxlbW9uYWRlIHRvIG1ha2UgYSBub24tYWxjb2hvbGljIHN1bW1lciBjdXAsIG9yIHVzZSBhcyBhIHN3ZWV0ZW5lciBpbiBjb29raW5nLjwvcD5cbjxoNSBjbGFzcz1cInByb2R1Y3QtZGV0YWlsLXRpdGxlXCI+VG8gc3RvcmU8YnI+XG48L2g1PlxuPHA+Rm9yIGJlc3QgYmVmb3JlIGRhdGUgc2VlIHBhY2suIFN0b3JlIGluIGEgY29vbCwgZHJ5IHBsYWNlLiBSZWZyaWdlcmF0ZSBhZnRlciBvcGVuaW5nIGFuZCB1c2Ugd2l0aGluIDYgd2Vla3M8YnI+PC9wPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5JbmdyZWRpZW50czwvaDU+XG48cD5BcHBsZXM8YnI+PC9wPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5BbGxlcmd5IGluZm9ybWF0aW9uPC9oNT5cbjxwPk5vIGFsbGVyZ2Vucy4gRnJlZSBmcm9tIHN1bHBoaXRlcy48L3A+XG48dGFibGUgd2lkdGg9XCIxMDAlXCI+XG48dGJvZHk+XG48dHI+XG48dGQ+PHN0cm9uZz5UeXBpY2FsIHZhbHVlczwvc3Ryb25nPjwvdGQ+XG48dGQ+PHN0cm9uZz5QZXIgMTAwZzwvc3Ryb25nPjwvdGQ+XG48L3RyPlxuPHRyPlxuPHRkPkVuZXJneTwvdGQ+XG48dGQ+MTA3MWtKICgyNTJrY2FsKTwvdGQ+XG48L3RyPlxuPHRyPlxuPHRkPkZhdDwvdGQ+XG48dGQ+MC4wZzwvdGQ+XG48L3RyPlxuPHRyPlxuPHRkPm9mIHdoaWNoIHNhdHVyYXRlczwvdGQ+XG48dGQ+MC4wZzwvdGQ+XG48L3RyPlxuPHRyPlxuPHRkPkNhcmJvaHlkcmF0ZTwvdGQ+XG48dGQ+NjIuOWc8L3RkPlxuPC90cj5cbjx0cj5cbjx0ZD5vZiB3aGljaCBzdWdhcnMqPC90ZD5cbjx0ZD41Mi44ZzwvdGQ+XG48L3RyPlxuPHRyPlxuPHRkPlByb3RlaW48L3RkPlxuPHRkPjAuMWc8L3RkPlxuPC90cj5cbjx0cj5cbjx0ZD5TYWx0PC90ZD5cbjx0ZD4wLjBnPC90ZD5cbjwvdHI+XG48L3Rib2R5PlxuPC90YWJsZT5cbjxoNSBjbGFzcz1cInByb2R1Y3QtZGV0YWlsLXRpdGxlXCI+TW9yZTwvaDU+XG48cD5Qcm9kdWN0IG9mwqBEb3JzZXQ8YnI+U3VpdGFibGUgZm9yIHZlZ2FucyBhbmQgdmVnZXRhcmlhbnM8YnI+PC9wPlxuPC9saT4iLCJkZmMtYjpoYXNRdWFudGl0eSI6Il86YjEyNyIsImRmYy1iOmltYWdlIjoiaHR0cHM6Ly9jZG4uc2hvcGlmeS5jb20vcy9maWxlcy8xLzA3MzEvODQ4My83OTM5L3Byb2R1Y3RzL0xpYmVydHktRmllbGRzLUFwcGxlLVN5cnVwLTIwMG1sX2IxZDFlOGNjLTk1MzAtNDBmZS05NmQ4LTVjNTJmNmRhOGEwMC5qcGc/dj0xNjc3NzYwNzc0IiwiZGZjLWI6bmFtZSI6IkFwcGxlIFN5cnVwIC0gUmV0YWlsIGJvdHRsZSwgMjUwbWwiLCJkZmMtYjpyZWZlcmVuY2VkQnkiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjYzNjkzMzEvQ2F0YWxvZ0l0ZW0ifSx7IkBpZCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjM2OTMzMS9Bc1BsYW5uZWRDb25zdW1wdGlvbkZsb3ciLCJAdHlwZSI6ImRmYy1iOkFzUGxhbm5lZENvbnN1bXB0aW9uRmxvdyIsImRmYy1iOmNvbnN1bWVzIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2MzY5MzMxIiwiZGZjLWI6aGFzUXVhbnRpdHkiOiJfOmIxMzUifSx7IkBpZCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjM2OTMzMS9Bc1BsYW5uZWRQcm9kdWN0aW9uRmxvdyIsIkB0eXBlIjoiZGZjLWI6QXNQbGFubmVkUHJvZHVjdGlvbkZsb3ciLCJkZmMtYjpoYXNRdWFudGl0eSI6Il86YjEzNiIsImRmYy1iOnByb2R1Y2VzIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2NDAyMDk5In0seyJAaWQiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjYzNjkzMzEvQXNQbGFubmVkVHJhbnNmb3JtYXRpb24iLCJAdHlwZSI6ImRmYy1iOkFzUGxhbm5lZFRyYW5zZm9ybWF0aW9uIiwiZGZjLWI6aGFzSW5jb21lIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2MzY5MzMxL0FzUGxhbm5lZENvbnN1bXB0aW9uRmxvdyIsImRmYy1iOmhhc091dGNvbWUiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjYzNjkzMzEvQXNQbGFubmVkUHJvZHVjdGlvbkZsb3cifSx7IkBpZCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjM2OTMzMS9DYXRhbG9nSXRlbSIsIkB0eXBlIjoiZGZjLWI6Q2F0YWxvZ0l0ZW0iLCJkZmMtYjpvZmZlcmVkVGhyb3VnaCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjM2OTMzMS9PZmZlciIsImRmYy1iOnNrdSI6IkxJQi9OQVNZUi9CVCIsImRmYy1iOnN0b2NrTGltaXRhdGlvbiI6Ii0xIn0seyJAaWQiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjYzNjkzMzEvT2ZmZXIiLCJAdHlwZSI6ImRmYy1iOk9mZmVyIiwiZGZjLWI6aGFzUHJpY2UiOnsiQGlkIjoiXzpiMTI4In19LHsiQGlkIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2NDAyMDk5IiwiQHR5cGUiOiJkZmMtYjpTdXBwbGllZFByb2R1Y3QiLCJkZmMtYjpkZXNjcmlwdGlvbiI6Ijx0YWJsZSB3aWR0aD1cIjEwMCVcIj5cbjx0Ym9keT5cbjx0ciBzdHlsZT1cImJvcmRlcjogMHB4O1wiIGRhdGEtbWNlLXN0eWxlPVwiYm9yZGVyOiAwcHg7XCI+XG48dGQgc3R5bGU9XCJjb2xvcjogIzAwMDAwMDsgYm9yZGVyOiAwcHg7IHdpZHRoOiA1MjZweDtcIiBiZ2NvbG9yPVwiI2Q2ZmJlZFwiIGRhdGEtbWNlLXN0eWxlPVwiY29sb3I6ICMwMDAwMDA7IGJvcmRlcjogMHB4OyB3aWR0aDogNTI2cHg7XCI+PGI+RG9yc2V0J3MgYW5zd2VyIHRvIG1hcGxlIHN5cnVwLCBMaWJlcnR5IEZpZWxkcycgQXBwbGUgU3lydXAgaXMgYSBsdXh1cmlvdXNseSByaWNoIGFuZCBpbnRlbnNlIG5hdHVyYWwgc3dlZXRlbmVyLCBtYWRlIG9ubHkgZnJvbSB0aGUgYXBwbGVzIG9mIHRoZWlyIG9yY2hhcmRzLjwvYj48L3RkPlxuPC90cj5cbjwvdGJvZHk+XG48L3RhYmxlPlxuPHA+TGliZXJ0eSBGaWVsZHMgcHJvZHVjZSBzbWFsbCBiYXRjaGVzIG9mIHN1cGVyYiBzeXJ1cCwgYmFsc2FtaWMgdmluZWdhciwgY2lkZXIgYW5kIHZvZGthIGJ5IGhhbmQgZnJvbSB0aGUgZnJ1aXQgb2YgdGhlaXIgb3duIERvcnNldCBhcHBsZSBvcmNoYXJkcywgcGxhbnRlZCBmcm9tIDIwMTAuPGJyPjwvcD5cbjxwPlRoZSBzeXJ1cCBjb250YWlucyBubyBhZGRpdGl2ZXMsIGNoZW1pY2FscywgZmluaW5nIGFnZW50cyBvciBzdWdhci4gT3ZlciAya2cgYXBwbGVzIGdvIGludG8gZWFjaCBib3R0bGUuPC9wPlxuPGxpIGlkPVwidGFiMVwiIGNsYXNzPVwiYWN0aXZlXCI+XG48aDUgY2xhc3M9XCJwcm9kdWN0LWRldGFpbC10aXRsZVwiPkhvdyB0byB1c2U8L2g1PlxuPHA+VXNlIGFzIGEgc3dlZXRlbmVyLCBsaWtlIG1hcGxlIHN5cnVwLsKgRGVsaWNpb3VzIHdpdGggcGFuY2FrZXMsIHlvZ2h1cnQgcG9ycmlkZ2UsIGljZSBjcmVhbSwgRnJlbmNoIHRvYXN0IGFuZCBtb3JlLiBNaXggd2l0aCBzcGFya2xpbmcgd2F0ZXIgb3IgbGVtb25hZGUgdG8gbWFrZSBhIG5vbi1hbGNvaG9saWMgc3VtbWVyIGN1cCwgb3IgdXNlIGFzIGEgc3dlZXRlbmVyIGluIGNvb2tpbmcuPC9wPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5UbyBzdG9yZTxicj5cbjwvaDU+XG48cD5Gb3IgYmVzdCBiZWZvcmUgZGF0ZSBzZWUgcGFjay4gU3RvcmUgaW4gYSBjb29sLCBkcnkgcGxhY2UuIFJlZnJpZ2VyYXRlIGFmdGVyIG9wZW5pbmcgYW5kIHVzZSB3aXRoaW4gNiB3ZWVrczxicj48L3A+XG48aDUgY2xhc3M9XCJwcm9kdWN0LWRldGFpbC10aXRsZVwiPkluZ3JlZGllbnRzPC9oNT5cbjxwPkFwcGxlczxicj48L3A+XG48aDUgY2xhc3M9XCJwcm9kdWN0LWRldGFpbC10aXRsZVwiPkFsbGVyZ3kgaW5mb3JtYXRpb248L2g1PlxuPHA+Tm8gYWxsZXJnZW5zLiBGcmVlIGZyb20gc3VscGhpdGVzLjwvcD5cbjx0YWJsZSB3aWR0aD1cIjEwMCVcIj5cbjx0Ym9keT5cbjx0cj5cbjx0ZD48c3Ryb25nPlR5cGljYWwgdmFsdWVzPC9zdHJvbmc+PC90ZD5cbjx0ZD48c3Ryb25nPlBlciAxMDBnPC9zdHJvbmc+PC90ZD5cbjwvdHI+XG48dHI+XG48dGQ+RW5lcmd5PC90ZD5cbjx0ZD4xMDcxa0ogKDI1MmtjYWwpPC90ZD5cbjwvdHI+XG48dHI+XG48dGQ+RmF0PC90ZD5cbjx0ZD4wLjBnPC90ZD5cbjwvdHI+XG48dHI+XG48dGQ+b2Ygd2hpY2ggc2F0dXJhdGVzPC90ZD5cbjx0ZD4wLjBnPC90ZD5cbjwvdHI+XG48dHI+XG48dGQ+Q2FyYm9oeWRyYXRlPC90ZD5cbjx0ZD42Mi45ZzwvdGQ+XG48L3RyPlxuPHRyPlxuPHRkPm9mIHdoaWNoIHN1Z2Fycyo8L3RkPlxuPHRkPjUyLjhnPC90ZD5cbjwvdHI+XG48dHI+XG48dGQ+UHJvdGVpbjwvdGQ+XG48dGQ+MC4xZzwvdGQ+XG48L3RyPlxuPHRyPlxuPHRkPlNhbHQ8L3RkPlxuPHRkPjAuMGc8L3RkPlxuPC90cj5cbjwvdGJvZHk+XG48L3RhYmxlPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5Nb3JlPC9oNT5cbjxwPlByb2R1Y3Qgb2bCoERvcnNldDxicj5TdWl0YWJsZSBmb3IgdmVnYW5zIGFuZCB2ZWdldGFyaWFuczxicj48L3A+XG48L2xpPiIsImRmYy1iOmhhc1F1YW50aXR5IjoiXzpiMTMxIiwiZGZjLWI6aW1hZ2UiOiJodHRwczovL2Nkbi5zaG9waWZ5LmNvbS9zL2ZpbGVzLzEvMDczMS84NDgzLzc5MzkvcHJvZHVjdHMvTGliZXJ0eS1GaWVsZHMtQXBwbGUtU3lydXAtMjAwbWxfYjFkMWU4Y2MtOTUzMC00MGZlLTk2ZDgtNWM1MmY2ZGE4YTAwLmpwZz92PTE2Nzc3NjA3NzQiLCJkZmMtYjpuYW1lIjoiQXBwbGUgU3lydXAgLSBDYXNlLCA2IHggMjUwbWwiLCJkZmMtYjpyZWZlcmVuY2VkQnkiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjY0MDIwOTkvQ2F0YWxvZ0l0ZW0ifSx7IkBpZCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjQwMjA5OS9DYXRhbG9nSXRlbSIsIkB0eXBlIjoiZGZjLWI6Q2F0YWxvZ0l0ZW0iLCJkZmMtYjpvZmZlcmVkVGhyb3VnaCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjQwMjA5OS9PZmZlciIsImRmYy1iOnNrdSI6IkxJQi9OQVNZUi9DNiIsImRmYy1iOnN0b2NrTGltaXRhdGlvbiI6Ii0xIn0seyJAaWQiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjY0MDIwOTkvT2ZmZXIiLCJAdHlwZSI6ImRmYy1iOk9mZmVyIiwiZGZjLWI6aGFzUHJpY2UiOnsiQGlkIjoiXzpiMTMyIn19XX0= + recorded_at: Fri, 02 Aug 2024 05:29:45 GMT recorded_with: VCR 6.2.0 diff --git a/spec/fixtures/vcr_cassettes/FdcRequest/refreshes_the_access_token_and_retrieves_a_catalog.yml b/spec/fixtures/vcr_cassettes/FdcRequest/refreshes_the_access_token_and_retrieves_a_catalog.yml index 636e061690..c96f019513 100644 --- a/spec/fixtures/vcr_cassettes/FdcRequest/refreshes_the_access_token_and_retrieves_a_catalog.yml +++ b/spec/fixtures/vcr_cassettes/FdcRequest/refreshes_the_access_token_and_retrieves_a_catalog.yml @@ -1,11 +1,11 @@ --- http_interactions: - request: - method: post - uri: https://food-data-collaboration-produc-fe870152f634.herokuapp.com/fdc/products?shop=test-hodmedod.myshopify.com + method: get + uri: https://env-0105831.jcloud-ver-jpe.ik-server.com/api/dfc/Enterprises/test-hodmedod/SuppliedProducts body: - encoding: UTF-8 - string: '{"userId":"testdfc@protonmail.com","accessToken":null}' + encoding: US-ASCII + string: '' headers: Content-Type: - application/json @@ -23,33 +23,27 @@ http_interactions: message: Forbidden headers: Server: - - Cowboy - Report-To: - - '{"group":"heroku-nel","max_age":3600,"endpoints":[{"url":"https://nel.heroku.com/reports?ts=1716515324&sid=812dcc77-0bd0-43b1-a5f1-b25750382959&s=QHSHL9RlLovwwwatlK4mrZMZ6powGfrf8MG7QDavBV4%3D"}]}' - Reporting-Endpoints: - - heroku-nel=https://nel.heroku.com/reports?ts=1716515324&sid=812dcc77-0bd0-43b1-a5f1-b25750382959&s=QHSHL9RlLovwwwatlK4mrZMZ6powGfrf8MG7QDavBV4%3D - Nel: - - '{"report_to":"heroku-nel","max_age":3600,"success_fraction":0.005,"failure_fraction":0.05,"response_headers":["Via"]}' + - openresty + Date: + - Fri, 02 Aug 2024 05:18:48 GMT + Content-Type: + - application/json; charset=utf-8 + Content-Length: + - '78' Connection: - keep-alive X-Powered-By: - Express Access-Control-Allow-Origin: - "*" - Content-Type: - - application/json; charset=utf-8 - Content-Length: - - '62' Etag: - - W/"3e-3yNPCMU4MDQmKmieGPWfDcA/0Eg" - Date: - - Fri, 24 May 2024 01:48:44 GMT - Via: - - 1.1 vegur + - W/"4e-vJeBLxgahmv23yP9gdPJW/woako" + Strict-Transport-Security: + - max-age=15811200 body: encoding: UTF-8 - string: '{"message":"User access denied","error":"User not authorized"}' - recorded_at: Fri, 24 May 2024 01:48:44 GMT + string: '{"message":"User access denied - token missing","error":"User not authorized"}' + recorded_at: Fri, 02 Aug 2024 05:18:49 GMT - request: method: get uri: https://login.lescommuns.org/auth/realms/data-food-consortium/.well-known/openid-configuration @@ -69,7 +63,7 @@ http_interactions: message: OK headers: Date: - - Fri, 24 May 2024 01:48:46 GMT + - Fri, 02 Aug 2024 05:18:50 GMT Content-Type: - application/json;charset=UTF-8 Transfer-Encoding: @@ -79,14 +73,14 @@ http_interactions: Vary: - Accept-Encoding Set-Cookie: - - AUTH_SESSION_ID=1716515327.317.9431.725800|6055218c9898cae39f8ffd531999e49a; + - AUTH_SESSION_ID=1722575931.041.145390.129545|78230f584c0d7db97d376e98de5321dc; Path=/; Secure; HttpOnly Cache-Control: - no-cache, must-revalidate, no-transform, no-store Referrer-Policy: - no-referrer Strict-Transport-Security: - - max-age=15724800; includeSubDomains + - max-age=31536000; includeSubDomains X-Content-Type-Options: - nosniff X-Frame-Options: @@ -97,7 +91,7 @@ http_interactions: encoding: ASCII-8BIT string: '{"issuer":"https://login.lescommuns.org/auth/realms/data-food-consortium","authorization_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/auth","token_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/token","introspection_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/token/introspect","userinfo_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/userinfo","end_session_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/logout","frontchannel_logout_session_supported":true,"frontchannel_logout_supported":true,"jwks_uri":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/certs","check_session_iframe":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/login-status-iframe.html","grant_types_supported":["authorization_code","implicit","refresh_token","password","client_credentials","urn:openid:params:grant-type:ciba","urn:ietf:params:oauth:grant-type:device_code"],"acr_values_supported":["0","1"],"response_types_supported":["code","none","id_token","token","id_token token","code id_token","code token","code id_token token"],"subject_types_supported":["public","pairwise"],"id_token_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512"],"id_token_encryption_alg_values_supported":["RSA-OAEP","RSA-OAEP-256","RSA1_5"],"id_token_encryption_enc_values_supported":["A256GCM","A192GCM","A128GCM","A128CBC-HS256","A192CBC-HS384","A256CBC-HS512"],"userinfo_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512","none"],"userinfo_encryption_alg_values_supported":["RSA-OAEP","RSA-OAEP-256","RSA1_5"],"userinfo_encryption_enc_values_supported":["A256GCM","A192GCM","A128GCM","A128CBC-HS256","A192CBC-HS384","A256CBC-HS512"],"request_object_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512","none"],"request_object_encryption_alg_values_supported":["RSA-OAEP","RSA-OAEP-256","RSA1_5"],"request_object_encryption_enc_values_supported":["A256GCM","A192GCM","A128GCM","A128CBC-HS256","A192CBC-HS384","A256CBC-HS512"],"response_modes_supported":["query","fragment","form_post","query.jwt","fragment.jwt","form_post.jwt","jwt"],"registration_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/clients-registrations/openid-connect","token_endpoint_auth_methods_supported":["private_key_jwt","client_secret_basic","client_secret_post","tls_client_auth","client_secret_jwt"],"token_endpoint_auth_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512"],"introspection_endpoint_auth_methods_supported":["private_key_jwt","client_secret_basic","client_secret_post","tls_client_auth","client_secret_jwt"],"introspection_endpoint_auth_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512"],"authorization_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512"],"authorization_encryption_alg_values_supported":["RSA-OAEP","RSA-OAEP-256","RSA1_5"],"authorization_encryption_enc_values_supported":["A256GCM","A192GCM","A128GCM","A128CBC-HS256","A192CBC-HS384","A256CBC-HS512"],"claims_supported":["aud","sub","iss","auth_time","name","given_name","family_name","preferred_username","email","acr"],"claim_types_supported":["normal"],"claims_parameter_supported":true,"scopes_supported":["openid","microprofile-jwt","phone","roles","profile","email","address","web-origins","acr","offline_access"],"request_parameter_supported":true,"request_uri_parameter_supported":true,"require_request_uri_registration":true,"code_challenge_methods_supported":["plain","S256"],"tls_client_certificate_bound_access_tokens":true,"revocation_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/revoke","revocation_endpoint_auth_methods_supported":["private_key_jwt","client_secret_basic","client_secret_post","tls_client_auth","client_secret_jwt"],"revocation_endpoint_auth_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512"],"backchannel_logout_supported":true,"backchannel_logout_session_supported":true,"device_authorization_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/auth/device","backchannel_token_delivery_modes_supported":["poll","ping"],"backchannel_authentication_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/ext/ciba/auth","backchannel_authentication_request_signing_alg_values_supported":["PS384","ES384","RS384","ES256","RS256","ES512","PS256","PS512","RS512"],"require_pushed_authorization_requests":false,"pushed_authorization_request_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/ext/par/request","mtls_endpoint_aliases":{"token_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/token","revocation_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/revoke","introspection_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/token/introspect","device_authorization_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/auth/device","registration_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/clients-registrations/openid-connect","userinfo_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/userinfo","pushed_authorization_request_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/ext/par/request","backchannel_authentication_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/ext/ciba/auth"},"authorization_response_iss_parameter_supported":true}' - recorded_at: Fri, 24 May 2024 01:48:46 GMT + recorded_at: Fri, 02 Aug 2024 05:18:50 GMT - request: method: post uri: https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/token @@ -121,7 +115,7 @@ http_interactions: message: OK headers: Date: - - Fri, 24 May 2024 01:48:47 GMT + - Fri, 02 Aug 2024 05:18:51 GMT Content-Type: - application/json Transfer-Encoding: @@ -131,7 +125,7 @@ http_interactions: Vary: - Accept-Encoding Set-Cookie: - - AUTH_SESSION_ID=1716515328.538.9431.297717|6055218c9898cae39f8ffd531999e49a; + - AUTH_SESSION_ID=1722575932.27.145456.739538|78230f584c0d7db97d376e98de5321dc; Path=/; Secure; HttpOnly Cache-Control: - no-store @@ -140,7 +134,7 @@ http_interactions: Referrer-Policy: - no-referrer Strict-Transport-Security: - - max-age=15724800; includeSubDomains + - max-age=31536000; includeSubDomains X-Content-Type-Options: - nosniff X-Frame-Options: @@ -149,15 +143,15 @@ http_interactions: - 1; mode=block body: encoding: ASCII-8BIT - string: '{"access_token":"","expires_in":1800,"refresh_expires_in":31373710,"refresh_token":"","token_type":"Bearer","id_token":"","not-before-policy":0,"session_state":"cfaa4a60-c2aa-4590-9fdf-a117f23d564f","scope":"openid + string: '{"access_token":"","expires_in":1800,"refresh_expires_in":31454045,"refresh_token":"","token_type":"Bearer","id_token":"","not-before-policy":0,"session_state":"207aea32-9912-47cb-b8ad-7508448912b8","scope":"openid profile email"}' - recorded_at: Fri, 24 May 2024 01:48:47 GMT + recorded_at: Fri, 02 Aug 2024 05:18:51 GMT - request: - method: post - uri: https://food-data-collaboration-produc-fe870152f634.herokuapp.com/fdc/products?shop=test-hodmedod.myshopify.com + method: get + uri: https://env-0105831.jcloud-ver-jpe.ik-server.com/api/dfc/Enterprises/test-hodmedod/SuppliedProducts body: - encoding: UTF-8 - string: '{"userId":"testdfc@protonmail.com","accessToken":""}' + encoding: US-ASCII + string: '' headers: Content-Type: - application/json @@ -175,32 +169,30 @@ http_interactions: message: OK headers: Server: - - Cowboy - Report-To: - - '{"group":"heroku-nel","max_age":3600,"endpoints":[{"url":"https://nel.heroku.com/reports?ts=1716515329&sid=812dcc77-0bd0-43b1-a5f1-b25750382959&s=db8%2Bqll%2F9ViX4tDoArQRI69fIFO5okGU%2F86h1whY9lM%3D"}]}' - Reporting-Endpoints: - - heroku-nel=https://nel.heroku.com/reports?ts=1716515329&sid=812dcc77-0bd0-43b1-a5f1-b25750382959&s=db8%2Bqll%2F9ViX4tDoArQRI69fIFO5okGU%2F86h1whY9lM%3D - Nel: - - '{"report_to":"heroku-nel","max_age":3600,"success_fraction":0.005,"failure_fraction":0.05,"response_headers":["Via"]}' + - openresty + Date: + - Fri, 02 Aug 2024 05:18:53 GMT + Content-Type: + - text/html; charset=utf-8 + Content-Length: + - '15329' Connection: - keep-alive X-Powered-By: - Express Access-Control-Allow-Origin: - "*" - Content-Type: - - application/json; charset=utf-8 - Content-Length: - - '41179' Etag: - - W/"a0db-ySojxiWOF5gtH86VVAw3VoRbZ/o" - Date: - - Fri, 24 May 2024 01:48:49 GMT - Via: - - 1.1 vegur + - W/"3be1-LGnj4P77pi1685bLhkZes+kuO7c" + Set-Cookie: + - SRVGROUP=common; path=/; HttpOnly + X-Resolver-Ip: + - 185.172.100.60 + Strict-Transport-Security: + - max-age=15811200 body: encoding: ASCII-8BIT string: !binary |- - eyJwcm9kdWN0cyI6IntcIkBjb250ZXh0XCI6XCJodHRwczovL3d3dy5kYXRhZm9vZGNvbnNvcnRpdW0ub3JnXCIsXCJAZ3JhcGhcIjpbe1wiQGlkXCI6XCJfOmI0M1wiLFwiQHR5cGVcIjpcImRmYy1iOlF1YW50aXRhdGl2ZVZhbHVlXCIsXCJkZmMtYjpoYXNVbml0XCI6XCJkZmMtbTpLaWxvZ3JhbVwiLFwiZGZjLWI6dmFsdWVcIjpcIjAuNFwifSx7XCJAaWRcIjpcIl86YjQ0XCIsXCJAdHlwZVwiOlwiZGZjLWI6UHJpY2VcIixcImRmYy1iOlZBVHJhdGVcIjpcIjBcIixcImRmYy1iOmhhc1VuaXRcIjpcImRmYy1tOkV1cm9cIixcImRmYy1iOnZhbHVlXCI6XCIyLjA5XCJ9LHtcIkBpZFwiOlwiXzpiNDVcIixcIkB0eXBlXCI6XCJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZVwiLFwiZGZjLWI6aGFzVW5pdFwiOlwiZGZjLW06S2lsb2dyYW1cIixcImRmYy1iOnZhbHVlXCI6XCIwLjRcIn0se1wiQGlkXCI6XCJfOmI0NlwiLFwiQHR5cGVcIjpcImRmYy1iOlByaWNlXCIsXCJkZmMtYjpWQVRyYXRlXCI6XCIwXCIsXCJkZmMtYjpoYXNVbml0XCI6XCJkZmMtbTpFdXJvXCIsXCJkZmMtYjp2YWx1ZVwiOlwiMS4xOVwifSx7XCJAaWRcIjpcIl86YjQ3XCIsXCJAdHlwZVwiOlwiZGZjLWI6UXVhbnRpdGF0aXZlVmFsdWVcIixcImRmYy1iOmhhc1VuaXRcIjpcImRmYy1tOktpbG9ncmFtXCIsXCJkZmMtYjp2YWx1ZVwiOlwiMC41XCJ9LHtcIkBpZFwiOlwiXzpiNDhcIixcIkB0eXBlXCI6XCJkZmMtYjpQcmljZVwiLFwiZGZjLWI6VkFUcmF0ZVwiOlwiMFwiLFwiZGZjLWI6aGFzVW5pdFwiOlwiZGZjLW06RXVyb1wiLFwiZGZjLWI6dmFsdWVcIjpcIjEuNjlcIn0se1wiQGlkXCI6XCJfOmI0OVwiLFwiQHR5cGVcIjpcImRmYy1iOlF1YW50aXRhdGl2ZVZhbHVlXCIsXCJkZmMtYjpoYXNVbml0XCI6XCJkZmMtbTpLaWxvZ3JhbVwiLFwiZGZjLWI6dmFsdWVcIjpcIjAuNFwifSx7XCJAaWRcIjpcIl86YjUwXCIsXCJAdHlwZVwiOlwiZGZjLWI6UHJpY2VcIixcImRmYy1iOlZBVHJhdGVcIjpcIjBcIixcImRmYy1iOmhhc1VuaXRcIjpcImRmYy1tOkV1cm9cIixcImRmYy1iOnZhbHVlXCI6XCIxLjM5XCJ9LHtcIkBpZFwiOlwiXzpiNTFcIixcIkB0eXBlXCI6XCJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZVwiLFwiZGZjLWI6aGFzVW5pdFwiOlwiZGZjLW06S2lsb2dyYW1cIixcImRmYy1iOnZhbHVlXCI6XCIwLjE3NVwifSx7XCJAaWRcIjpcIl86YjUyXCIsXCJAdHlwZVwiOlwiZGZjLWI6UHJpY2VcIixcImRmYy1iOlZBVHJhdGVcIjpcIjBcIixcImRmYy1iOmhhc1VuaXRcIjpcImRmYy1tOkV1cm9cIixcImRmYy1iOnZhbHVlXCI6XCIyLjg5XCJ9LHtcIkBpZFwiOlwiXzpiNTNcIixcIkB0eXBlXCI6XCJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZVwiLFwiZGZjLWI6aGFzVW5pdFwiOlwiZGZjLW06S2lsb2dyYW1cIixcImRmYy1iOnZhbHVlXCI6XCIwLjRcIn0se1wiQGlkXCI6XCJfOmI1NFwiLFwiQHR5cGVcIjpcImRmYy1iOlByaWNlXCIsXCJkZmMtYjpWQVRyYXRlXCI6XCIwXCIsXCJkZmMtYjpoYXNVbml0XCI6XCJkZmMtbTpFdXJvXCIsXCJkZmMtYjp2YWx1ZVwiOlwiMC45OVwifSx7XCJAaWRcIjpcIl86YjU1XCIsXCJAdHlwZVwiOlwiZGZjLWI6UXVhbnRpdGF0aXZlVmFsdWVcIixcImRmYy1iOmhhc1VuaXRcIjpcImRmYy1tOktpbG9ncmFtXCIsXCJkZmMtYjp2YWx1ZVwiOlwiMC4zXCJ9LHtcIkBpZFwiOlwiXzpiNTZcIixcIkB0eXBlXCI6XCJkZmMtYjpQcmljZVwiLFwiZGZjLWI6VkFUcmF0ZVwiOlwiMFwiLFwiZGZjLWI6aGFzVW5pdFwiOlwiZGZjLW06RXVyb1wiLFwiZGZjLWI6dmFsdWVcIjpcIjIuOTlcIn0se1wiQGlkXCI6XCJfOmI1N1wiLFwiQHR5cGVcIjpcImRmYy1iOlF1YW50aXRhdGl2ZVZhbHVlXCIsXCJkZmMtYjpoYXNVbml0XCI6XCJkZmMtbTpLaWxvZ3JhbVwiLFwiZGZjLWI6dmFsdWVcIjpcIjQuOFwifSx7XCJAaWRcIjpcIl86YjU4XCIsXCJAdHlwZVwiOlwiZGZjLWI6UHJpY2VcIixcImRmYy1iOlZBVHJhdGVcIjpcIjBcIixcImRmYy1iOmhhc1VuaXRcIjpcImRmYy1tOkV1cm9cIixcImRmYy1iOnZhbHVlXCI6XCIxOC44NVwifSx7XCJAaWRcIjpcIl86YjU5XCIsXCJAdHlwZVwiOlwiZGZjLWI6UXVhbnRpdGF0aXZlVmFsdWVcIixcImRmYy1iOmhhc1VuaXRcIjpcImRmYy1tOktpbG9ncmFtXCIsXCJkZmMtYjp2YWx1ZVwiOlwiNC44XCJ9LHtcIkBpZFwiOlwiXzpiNjBcIixcIkB0eXBlXCI6XCJkZmMtYjpQcmljZVwiLFwiZGZjLWI6VkFUcmF0ZVwiOlwiMFwiLFwiZGZjLWI6aGFzVW5pdFwiOlwiZGZjLW06RXVyb1wiLFwiZGZjLWI6dmFsdWVcIjpcIjcuNDJcIn0se1wiQGlkXCI6XCJfOmI2MVwiLFwiQHR5cGVcIjpcImRmYy1iOlF1YW50aXRhdGl2ZVZhbHVlXCIsXCJkZmMtYjpoYXNVbml0XCI6XCJkZmMtbTpLaWxvZ3JhbVwiLFwiZGZjLWI6dmFsdWVcIjpcIjVcIn0se1wiQGlkXCI6XCJfOmI2MlwiLFwiQHR5cGVcIjpcImRmYy1iOlByaWNlXCIsXCJkZmMtYjpWQVRyYXRlXCI6XCIwXCIsXCJkZmMtYjpoYXNVbml0XCI6XCJkZmMtbTpFdXJvXCIsXCJkZmMtYjp2YWx1ZVwiOlwiMTIuNjBcIn0se1wiQGlkXCI6XCJfOmI2M1wiLFwiQHR5cGVcIjpcImRmYy1iOlF1YW50aXRhdGl2ZVZhbHVlXCIsXCJkZmMtYjpoYXNVbml0XCI6XCJkZmMtbTpLaWxvZ3JhbVwiLFwiZGZjLWI6dmFsdWVcIjpcIjQuOFwifSx7XCJAaWRcIjpcIl86YjY0XCIsXCJAdHlwZVwiOlwiZGZjLWI6UHJpY2VcIixcImRmYy1iOlZBVHJhdGVcIjpcIjBcIixcImRmYy1iOmhhc1VuaXRcIjpcImRmYy1tOkV1cm9cIixcImRmYy1iOnZhbHVlXCI6XCI4Ljc2XCJ9LHtcIkBpZFwiOlwiXzpiNjVcIixcIkB0eXBlXCI6XCJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZVwiLFwiZGZjLWI6aGFzVW5pdFwiOlwiZGZjLW06S2lsb2dyYW1cIixcImRmYy1iOnZhbHVlXCI6XCIxLjA1XCJ9LHtcIkBpZFwiOlwiXzpiNjZcIixcIkB0eXBlXCI6XCJkZmMtYjpQcmljZVwiLFwiZGZjLWI6VkFUcmF0ZVwiOlwiMFwiLFwiZGZjLWI6aGFzVW5pdFwiOlwiZGZjLW06RXVyb1wiLFwiZGZjLWI6dmFsdWVcIjpcIjEzLjA1XCJ9LHtcIkBpZFwiOlwiXzpiNjdcIixcIkB0eXBlXCI6XCJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZVwiLFwiZGZjLWI6aGFzVW5pdFwiOlwiZGZjLW06S2lsb2dyYW1cIixcImRmYy1iOnZhbHVlXCI6XCI0LjhcIn0se1wiQGlkXCI6XCJfOmI2OFwiLFwiQHR5cGVcIjpcImRmYy1iOlByaWNlXCIsXCJkZmMtYjpWQVRyYXRlXCI6XCIwXCIsXCJkZmMtYjpoYXNVbml0XCI6XCJkZmMtbTpFdXJvXCIsXCJkZmMtYjp2YWx1ZVwiOlwiNi43NlwifSx7XCJAaWRcIjpcIl86YjY5XCIsXCJAdHlwZVwiOlwiZGZjLWI6UXVhbnRpdGF0aXZlVmFsdWVcIixcImRmYy1iOmhhc1VuaXRcIjpcImRmYy1tOktpbG9ncmFtXCIsXCJkZmMtYjp2YWx1ZVwiOlwiM1wifSx7XCJAaWRcIjpcIl86YjcwXCIsXCJAdHlwZVwiOlwiZGZjLWI6UHJpY2VcIixcImRmYy1iOlZBVHJhdGVcIjpcIjBcIixcImRmYy1iOmhhc1VuaXRcIjpcImRmYy1tOkV1cm9cIixcImRmYy1iOnZhbHVlXCI6XCIxNS45MFwifSx7XCJAaWRcIjpcIl86YjcxXCIsXCJAdHlwZVwiOlwiZGZjLWI6UXVhbnRpdGF0aXZlVmFsdWVcIixcImRmYy1iOmhhc1VuaXRcIjpcImRmYy1tOlBpZWNlXCIsXCJkZmMtYjp2YWx1ZVwiOlwiMTJcIn0se1wiQGlkXCI6XCJfOmI3MlwiLFwiQHR5cGVcIjpcImRmYy1iOlF1YW50aXRhdGl2ZVZhbHVlXCIsXCJkZmMtYjpoYXNVbml0XCI6XCJkZmMtbTpQaWVjZVwiLFwiZGZjLWI6dmFsdWVcIjpcIjFcIn0se1wiQGlkXCI6XCJfOmI3M1wiLFwiQHR5cGVcIjpcImRmYy1iOlF1YW50aXRhdGl2ZVZhbHVlXCIsXCJkZmMtYjpoYXNVbml0XCI6XCJkZmMtbTpQaWVjZVwiLFwiZGZjLWI6dmFsdWVcIjpcIjEyXCJ9LHtcIkBpZFwiOlwiXzpiNzRcIixcIkB0eXBlXCI6XCJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZVwiLFwiZGZjLWI6aGFzVW5pdFwiOlwiZGZjLW06UGllY2VcIixcImRmYy1iOnZhbHVlXCI6XCIxXCJ9LHtcIkBpZFwiOlwiXzpiNzVcIixcIkB0eXBlXCI6XCJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZVwiLFwiZGZjLWI6aGFzVW5pdFwiOlwiZGZjLW06UGllY2VcIixcImRmYy1iOnZhbHVlXCI6XCIxMFwifSx7XCJAaWRcIjpcIl86Yjc2XCIsXCJAdHlwZVwiOlwiZGZjLWI6UXVhbnRpdGF0aXZlVmFsdWVcIixcImRmYy1iOmhhc1VuaXRcIjpcImRmYy1tOlBpZWNlXCIsXCJkZmMtYjp2YWx1ZVwiOlwiMVwifSx7XCJAaWRcIjpcIl86Yjc3XCIsXCJAdHlwZVwiOlwiZGZjLWI6UXVhbnRpdGF0aXZlVmFsdWVcIixcImRmYy1iOmhhc1VuaXRcIjpcImRmYy1tOlBpZWNlXCIsXCJkZmMtYjp2YWx1ZVwiOlwiMTJcIn0se1wiQGlkXCI6XCJfOmI3OFwiLFwiQHR5cGVcIjpcImRmYy1iOlF1YW50aXRhdGl2ZVZhbHVlXCIsXCJkZmMtYjpoYXNVbml0XCI6XCJkZmMtbTpQaWVjZVwiLFwiZGZjLWI6dmFsdWVcIjpcIjFcIn0se1wiQGlkXCI6XCJfOmI3OVwiLFwiQHR5cGVcIjpcImRmYy1iOlF1YW50aXRhdGl2ZVZhbHVlXCIsXCJkZmMtYjpoYXNVbml0XCI6XCJkZmMtbTpQaWVjZVwiLFwiZGZjLWI6dmFsdWVcIjpcIjZcIn0se1wiQGlkXCI6XCJfOmI4MFwiLFwiQHR5cGVcIjpcImRmYy1iOlF1YW50aXRhdGl2ZVZhbHVlXCIsXCJkZmMtYjpoYXNVbml0XCI6XCJkZmMtbTpQaWVjZVwiLFwiZGZjLWI6dmFsdWVcIjpcIjFcIn0se1wiQGlkXCI6XCJfOmI4MVwiLFwiQHR5cGVcIjpcImRmYy1iOlF1YW50aXRhdGl2ZVZhbHVlXCIsXCJkZmMtYjpoYXNVbml0XCI6XCJkZmMtbTpQaWVjZVwiLFwiZGZjLWI6dmFsdWVcIjpcIjEyXCJ9LHtcIkBpZFwiOlwiXzpiODJcIixcIkB0eXBlXCI6XCJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZVwiLFwiZGZjLWI6aGFzVW5pdFwiOlwiZGZjLW06UGllY2VcIixcImRmYy1iOnZhbHVlXCI6XCIxXCJ9LHtcIkBpZFwiOlwiXzpiODNcIixcIkB0eXBlXCI6XCJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZVwiLFwiZGZjLWI6aGFzVW5pdFwiOlwiZGZjLW06UGllY2VcIixcImRmYy1iOnZhbHVlXCI6XCIxMFwifSx7XCJAaWRcIjpcIl86Yjg0XCIsXCJAdHlwZVwiOlwiZGZjLWI6UXVhbnRpdGF0aXZlVmFsdWVcIixcImRmYy1iOmhhc1VuaXRcIjpcImRmYy1tOlBpZWNlXCIsXCJkZmMtYjp2YWx1ZVwiOlwiMVwifSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MTk0NjY0Njc2MzVcIixcIkB0eXBlXCI6XCJkZmMtYjpTdXBwbGllZFByb2R1Y3RcIixcImRmYy1iOmRlc2NyaXB0aW9uXCI6XCI8dGFibGUgd2lkdGg9XFxcIjEwMCVcXFwiPlxcbjx0Ym9keT5cXG48dHIgc3R5bGU9XFxcImJvcmRlcjogMHB4O1xcXCI+XFxuPHRkIGJnY29sb3I9XFxcIiNkNmZiZWRcXFwiIHN0eWxlPVxcXCJjb2xvcjogIzAwMDAwMDsgYm9yZGVyOiAwcHg7XFxcIj48c3Ryb25nPlRoZXkncmUgYmFjayE8L3N0cm9uZz48L3RkPlxcbjwvdHI+XFxuPC90Ym9keT5cXG48L3RhYmxlPlxcbjxwPjxzdHJvbmc+VGhpbmsgYmFrZWQgYmVhbnMgYXJlIEJyaXRpc2g/IFRoZXkgYXJlIG5vdyEgV2UgdXNlIG9ubHkgQnJpdGlzaC1ncm93biBmYXZhIGJlYW5zIC0gQnJpdGFpbidzIG9yaWdpbmFsIGJlYW4sIGdyb3duIGhlcmUgc2luY2UgdGhlIElyb24gQWdlLiBPdXIgQmFrZWQgQnJpdGlzaCBCZWFucyBhcmUgZGVsaWNpb3VzbHkgZGlmZmVyZW50LCB3aXRoIGxhcmdlIG1lYXR5IGZhdmEgYmVhbnMgaW4gYSB0YXN0eSB0b21hdG8gc2F1Y2UuPC9zdHJvbmc+PC9wPlxcbjxwPjxzdHJvbmc+PGEgdGl0bGU9XFxcIldoYXQgYXJlIGZhdmEgYmVhbnM/IEFyZW4ndCB0aGV5IGp1c3QgYnJvYWQgYmVhbnM/XFxcIiBocmVmPVxcXCIvYmxvZ3MvbmV3cy93aGF0LWFyZS1mYXZhLWJlYW5zLWFyZS10aGV5LWp1c3QtYnJvYWQtYmVhbnNcXFwiIGRhdGEtbWNlLWZyYWdtZW50PVxcXCIxXFxcIiBkYXRhLW1jZS1ocmVmPVxcXCIvYmxvZ3MvbmV3cy93aGF0LWFyZS1mYXZhLWJlYW5zLWFyZS10aGV5LWp1c3QtYnJvYWQtYmVhbnNcXFwiPldoYXQgYXJlIGZhdmEgYmVhbnM/IEZpbmQgb3V0IGhlcmUuLi48L2E+PC9zdHJvbmc+PC9wPlxcbjwhLS0gc3BsaXQgLS0+PGgzPkNvbXBsZXRlIFByb2R1Y3QgRGV0YWlsczwvaDM+PHA+T3VyIEJha2VkIEJyaXRpc2ggQmVhbnMgYXJlIGNvb2tlZCBhbmQgcmVhZHkgdG8gZWF0LCBob3Qgb3IgY29sZC4gVGhleSdyZSBnb29kIHNlcnZlZCBvbiB0b2FzdCBidXQgYWxzbyBkZWxpY2lvdXMgYWRkZWQgdG8gc3Rld3MsIGN1cnJpZXMgb3IgY2Fzc2Vyb2xlcy4gT3IgZXZlbiBpbiBhIHBpZS48L3A+XFxuPGg1IGNsYXNzPVxcXCJwcm9kdWN0LWRldGFpbC10aXRsZVxcXCI+Q29va2luZyBpbnN0cnVjdGlvbnM8L2g1PlxcbjxwPjxzdHJvbmc+Q29va2luZyBvbiB0aGUgSG9iPC9zdHJvbmc+PGJyPkVtcHR5IGNvbnRlbnRzIGludG8gc2F1Y2VwYW4uIEhlYXQgZ2VudGx5IGZvciA0LTUgbWludXRlcyB3aGlsZSBzdGlycmluZy4gRm9yIGJlc3QgZmxhdm91ciBkbyBub3QgYm9pbCBvciBvdmVyY29vay4gRG8gbm90IHJlaGVhdC48L3A+XFxuPHA+PHN0cm9uZz5NaWNyb3dhdmUgQ29va2luZzwvc3Ryb25nPjxicj5FbXB0eSBjb250ZW50cyBpbnRvIGEgbm9uLW1ldGFsbGljIGJvd2wgYW5kIGNvdmVyLiBIZWF0IGZvciAyIHRvIDMgbWludXRlcywgc3RpcnJpbmcgaGFsZndheS4gQ2hlY2sgdGhlIGZvb2QgaXMgaG90LCBzdGlyIHdlbGwgYW5kIHNlcnZlLiBEbyBub3QgcmVoZWF0LjwvcD5cXG48aDUgY2xhc3M9XFxcInByb2R1Y3QtZGV0YWlsLXRpdGxlXFxcIj5UbyBTdG9yZTwvaDU+XFxuPHA+U3RvcmUgaW4gYSBjb29sLCBkcnkgcGxhY2UuIE9uY2Ugb3BlbmVkLCB0cmFuc2ZlciBjb250ZW50cyB0byBhIG5vbi1tZXRhbGxpYyBjb250YWluZXIsIGNvdmVyIHJlZnJpZ2VyYXRlIGFuZCB1c2Ugd2l0aCAyIGRheXMuPC9wPlxcbjxoNSBjbGFzcz1cXFwicHJvZHVjdC1kZXRhaWwtdGl0bGVcXFwiPkluZ3JlZGllbnRzPC9oNT5cXG48cD5GYXZhIEJlYW5zIChCcm9hZCBCZWFucykgKDQyJSksIFdhdGVyLCBUb21hdG8gUHVyZWUsIFN1Z2FyLCBNb2RpZmllZCBNYWl6ZSBTdGFyY2gsIFNhbHQsIEhlcmJzICZhbXA7IFNwaWNlcywgQ29uY2VudHJhdGVkIExlbW9uIEp1aWNlPC9wPlxcbjxoNSBjbGFzcz1cXFwicHJvZHVjdC1kZXRhaWwtdGl0bGVcXFwiPkFsbGVyZ3kgaW5mb3JtYXRpb248L2g1PlxcbjxwPk5vIEFsbGVyZ2VuczwvcD5cXG48dGFibGUgd2lkdGg9XFxcIjEwMCVcXFwiPlxcbjx0Ym9keT5cXG48dHI+XFxuPHRkPjxzdHJvbmc+VHlwaWNhbCB2YWx1ZXM8L3N0cm9uZz48L3RkPlxcbjx0ZD48c3Ryb25nPlBlciAxMDBnPC9zdHJvbmc+PC90ZD5cXG48L3RyPlxcbjx0cj5cXG48dGQ+RW5lcmd5PC90ZD5cXG48dGQ+Mjkya0ogKDY5a2NhbCk8L3RkPlxcbjwvdHI+XFxuPHRyPlxcbjx0ZD5GYXQ8L3RkPlxcbjx0ZD4wLjRnPC90ZD5cXG48L3RyPlxcbjx0cj5cXG48dGQ+b2Ygd2hpY2ggc2F0dXJhdGVzPC90ZD5cXG48dGQ+MC4xZzwvdGQ+XFxuPC90cj5cXG48dHI+XFxuPHRkPkNhcmJvaHlkcmF0ZTwvdGQ+XFxuPHRkPjEwLjFnPC90ZD5cXG48L3RyPlxcbjx0cj5cXG48dGQ+b2Ygd2hpY2ggc3VnYXJzPC90ZD5cXG48dGQ+NC42ZzwvdGQ+XFxuPC90cj5cXG48dHI+XFxuPHRkPkZpYnJlPC90ZD5cXG48dGQ+NWc8L3RkPlxcbjwvdHI+XFxuPHRyPlxcbjx0ZD5Qcm90ZWluPC90ZD5cXG48dGQ+NGc8L3RkPlxcbjwvdHI+XFxuPHRyPlxcbjx0ZD5TYWx0PC90ZD5cXG48dGQ+MC42ZzwvdGQ+XFxuPC90cj5cXG48L3Rib2R5PlxcbjwvdGFibGU+PGg1IGNsYXNzPVxcXCJwcm9kdWN0LWRldGFpbC10aXRsZVxcXCI+TW9yZTwvaDU+XFxuPHA+RGVsaWNpb3VzLCBudXRyaXRpb3VzIGFuZCBnb29kIGZvciB0aGUgc29pbCwgZmF2YSBiZWFucyBhcmUgYSB2YXJpZXR5IG9mIGJyb2FkIGJlYW4sIFZpY2lhIGZhYmEsIGxlZnQgdG8gcmlwZW4gYW5kIGRyeSBiZWZvcmUgaGFydmVzdC4gVGhleeKAmXJlIGFsc28ga25vd24gYXMgZmllbGQgYmVhbnMsIGhvcnNlIGJlYW5zLCBXaW5kc29yIGJlYW5zIG9yIGZ1bC48L3A+XFxuPHA+U3VpdGFibGUgZm9yIHZlZ2FucyBhbmQgdmVnZXRhcmlhbnM8L3A+XFxuXCIsXCJkZmMtYjpoYXNRdWFudGl0eVwiOlwiXzpiNDNcIixcImRmYy1iOmltYWdlXCI6XCJodHRwczovL2Nkbi5zaG9waWZ5LmNvbS9zL2ZpbGVzLzEvMDczMS84NDgzLzc5MzkvcHJvZHVjdHMvUGFjay1DYW4tQmFrZWQtQmVhbnMtMTgwMHg2Xzk4M3g2NTZfNTEzNzU4ZTYtMjYxNi00Njg3LWE4YjItYmE2ZGRlODY0OTIzLmpwZz92PTE2Nzc3NjA3NzhcIixcImRmYy1iOm5hbWVcIjpcIkJha2VkIEJyaXRpc2ggQmVhbnMgLSBSZXRhaWwgY2FuLCA0MDBnIChjYW4pXCIsXCJkZmMtYjpyZWZlcmVuY2VkQnlcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MTk0NjY0Njc2MzUvY2F0YWxvZ0l0ZW1cIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDY2NDY3NjM1L2NhdGFsb2dJdGVtXCIsXCJAdHlwZVwiOlwiZGZjLWI6Q2F0YWxvZ0l0ZW1cIixcImRmYy1iOm9mZmVyZWRUaHJvdWdoXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDY2NDY3NjM1L29mZmVyXCIsXCJkZmMtYjpza3VcIjpcIk5DQkIvVDRcIixcImRmYy1iOnN0b2NrTGltaXRhdGlvblwiOlwiLTFcIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDY2NDY3NjM1L29mZmVyXCIsXCJAdHlwZVwiOlwiZGZjLWI6T2ZmZXJcIixcImRmYy1iOmhhc1ByaWNlXCI6e1wiQGlkXCI6XCJfOmI0NFwifX0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDY2NDY3NjM1L3BsYW5uZWRDb25zdW1wdGlvbkZsb3dcIixcIkB0eXBlXCI6XCJkZmMtYjpBc1BsYW5uZWRDb25zdW1wdGlvbkZsb3dcIixcImRmYy1iOmNvbnN1bWVzXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDY2NDY3NjM1XCIsXCJkZmMtYjpoYXNRdWFudGl0eVwiOlwiXzpiNzFcIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDY2NDY3NjM1L3BsYW5uZWRQcm9kdWN0aW9uRmxvd1wiLFwiQHR5cGVcIjpcImRmYy1iOkFzUGxhbm5lZFByb2R1Y3Rpb25GbG93XCIsXCJkZmMtYjpoYXNRdWFudGl0eVwiOlwiXzpiNzJcIixcImRmYy1iOnByb2R1Y2VzXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDY2NTAwNDAzXCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ2NjQ2NzYzNS90cmFuc2Zvcm1hdGlvblwiLFwiQHR5cGVcIjpcImRmYy1iOkFzUGxhbm5lZFRyYW5zZm9ybWF0aW9uXCIsXCJkZmMtYjpoYXNJbmNvbWVcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MTk0NjY0Njc2MzUvcGxhbm5lZENvbnN1bXB0aW9uRmxvd1wiLFwiZGZjLWI6aGFzT3V0Y29tZVwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ2NjQ2NzYzNS9wbGFubmVkUHJvZHVjdGlvbkZsb3dcIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDY2NTAwNDAzXCIsXCJAdHlwZVwiOlwiZGZjLWI6U3VwcGxpZWRQcm9kdWN0XCIsXCJkZmMtYjpkZXNjcmlwdGlvblwiOlwiPHRhYmxlIHdpZHRoPVxcXCIxMDAlXFxcIj5cXG48dGJvZHk+XFxuPHRyIHN0eWxlPVxcXCJib3JkZXI6IDBweDtcXFwiPlxcbjx0ZCBiZ2NvbG9yPVxcXCIjZDZmYmVkXFxcIiBzdHlsZT1cXFwiY29sb3I6ICMwMDAwMDA7IGJvcmRlcjogMHB4O1xcXCI+PHN0cm9uZz5UaGV5J3JlIGJhY2shPC9zdHJvbmc+PC90ZD5cXG48L3RyPlxcbjwvdGJvZHk+XFxuPC90YWJsZT5cXG48cD48c3Ryb25nPlRoaW5rIGJha2VkIGJlYW5zIGFyZSBCcml0aXNoPyBUaGV5IGFyZSBub3chIFdlIHVzZSBvbmx5IEJyaXRpc2gtZ3Jvd24gZmF2YSBiZWFucyAtIEJyaXRhaW4ncyBvcmlnaW5hbCBiZWFuLCBncm93biBoZXJlIHNpbmNlIHRoZSBJcm9uIEFnZS4gT3VyIEJha2VkIEJyaXRpc2ggQmVhbnMgYXJlIGRlbGljaW91c2x5IGRpZmZlcmVudCwgd2l0aCBsYXJnZSBtZWF0eSBmYXZhIGJlYW5zIGluIGEgdGFzdHkgdG9tYXRvIHNhdWNlLjwvc3Ryb25nPjwvcD5cXG48cD48c3Ryb25nPjxhIHRpdGxlPVxcXCJXaGF0IGFyZSBmYXZhIGJlYW5zPyBBcmVuJ3QgdGhleSBqdXN0IGJyb2FkIGJlYW5zP1xcXCIgaHJlZj1cXFwiL2Jsb2dzL25ld3Mvd2hhdC1hcmUtZmF2YS1iZWFucy1hcmUtdGhleS1qdXN0LWJyb2FkLWJlYW5zXFxcIiBkYXRhLW1jZS1mcmFnbWVudD1cXFwiMVxcXCIgZGF0YS1tY2UtaHJlZj1cXFwiL2Jsb2dzL25ld3Mvd2hhdC1hcmUtZmF2YS1iZWFucy1hcmUtdGhleS1qdXN0LWJyb2FkLWJlYW5zXFxcIj5XaGF0IGFyZSBmYXZhIGJlYW5zPyBGaW5kIG91dCBoZXJlLi4uPC9hPjwvc3Ryb25nPjwvcD5cXG48IS0tIHNwbGl0IC0tPjxoMz5Db21wbGV0ZSBQcm9kdWN0IERldGFpbHM8L2gzPjxwPk91ciBCYWtlZCBCcml0aXNoIEJlYW5zIGFyZSBjb29rZWQgYW5kIHJlYWR5IHRvIGVhdCwgaG90IG9yIGNvbGQuIFRoZXkncmUgZ29vZCBzZXJ2ZWQgb24gdG9hc3QgYnV0IGFsc28gZGVsaWNpb3VzIGFkZGVkIHRvIHN0ZXdzLCBjdXJyaWVzIG9yIGNhc3Nlcm9sZXMuIE9yIGV2ZW4gaW4gYSBwaWUuPC9wPlxcbjxoNSBjbGFzcz1cXFwicHJvZHVjdC1kZXRhaWwtdGl0bGVcXFwiPkNvb2tpbmcgaW5zdHJ1Y3Rpb25zPC9oNT5cXG48cD48c3Ryb25nPkNvb2tpbmcgb24gdGhlIEhvYjwvc3Ryb25nPjxicj5FbXB0eSBjb250ZW50cyBpbnRvIHNhdWNlcGFuLiBIZWF0IGdlbnRseSBmb3IgNC01IG1pbnV0ZXMgd2hpbGUgc3RpcnJpbmcuIEZvciBiZXN0IGZsYXZvdXIgZG8gbm90IGJvaWwgb3Igb3ZlcmNvb2suIERvIG5vdCByZWhlYXQuPC9wPlxcbjxwPjxzdHJvbmc+TWljcm93YXZlIENvb2tpbmc8L3N0cm9uZz48YnI+RW1wdHkgY29udGVudHMgaW50byBhIG5vbi1tZXRhbGxpYyBib3dsIGFuZCBjb3Zlci4gSGVhdCBmb3IgMiB0byAzIG1pbnV0ZXMsIHN0aXJyaW5nIGhhbGZ3YXkuIENoZWNrIHRoZSBmb29kIGlzIGhvdCwgc3RpciB3ZWxsIGFuZCBzZXJ2ZS4gRG8gbm90IHJlaGVhdC48L3A+XFxuPGg1IGNsYXNzPVxcXCJwcm9kdWN0LWRldGFpbC10aXRsZVxcXCI+VG8gU3RvcmU8L2g1PlxcbjxwPlN0b3JlIGluIGEgY29vbCwgZHJ5IHBsYWNlLiBPbmNlIG9wZW5lZCwgdHJhbnNmZXIgY29udGVudHMgdG8gYSBub24tbWV0YWxsaWMgY29udGFpbmVyLCBjb3ZlciByZWZyaWdlcmF0ZSBhbmQgdXNlIHdpdGggMiBkYXlzLjwvcD5cXG48aDUgY2xhc3M9XFxcInByb2R1Y3QtZGV0YWlsLXRpdGxlXFxcIj5JbmdyZWRpZW50czwvaDU+XFxuPHA+RmF2YSBCZWFucyAoQnJvYWQgQmVhbnMpICg0MiUpLCBXYXRlciwgVG9tYXRvIFB1cmVlLCBTdWdhciwgTW9kaWZpZWQgTWFpemUgU3RhcmNoLCBTYWx0LCBIZXJicyAmYW1wOyBTcGljZXMsIENvbmNlbnRyYXRlZCBMZW1vbiBKdWljZTwvcD5cXG48aDUgY2xhc3M9XFxcInByb2R1Y3QtZGV0YWlsLXRpdGxlXFxcIj5BbGxlcmd5IGluZm9ybWF0aW9uPC9oNT5cXG48cD5ObyBBbGxlcmdlbnM8L3A+XFxuPHRhYmxlIHdpZHRoPVxcXCIxMDAlXFxcIj5cXG48dGJvZHk+XFxuPHRyPlxcbjx0ZD48c3Ryb25nPlR5cGljYWwgdmFsdWVzPC9zdHJvbmc+PC90ZD5cXG48dGQ+PHN0cm9uZz5QZXIgMTAwZzwvc3Ryb25nPjwvdGQ+XFxuPC90cj5cXG48dHI+XFxuPHRkPkVuZXJneTwvdGQ+XFxuPHRkPjI5MmtKICg2OWtjYWwpPC90ZD5cXG48L3RyPlxcbjx0cj5cXG48dGQ+RmF0PC90ZD5cXG48dGQ+MC40ZzwvdGQ+XFxuPC90cj5cXG48dHI+XFxuPHRkPm9mIHdoaWNoIHNhdHVyYXRlczwvdGQ+XFxuPHRkPjAuMWc8L3RkPlxcbjwvdHI+XFxuPHRyPlxcbjx0ZD5DYXJib2h5ZHJhdGU8L3RkPlxcbjx0ZD4xMC4xZzwvdGQ+XFxuPC90cj5cXG48dHI+XFxuPHRkPm9mIHdoaWNoIHN1Z2FyczwvdGQ+XFxuPHRkPjQuNmc8L3RkPlxcbjwvdHI+XFxuPHRyPlxcbjx0ZD5GaWJyZTwvdGQ+XFxuPHRkPjVnPC90ZD5cXG48L3RyPlxcbjx0cj5cXG48dGQ+UHJvdGVpbjwvdGQ+XFxuPHRkPjRnPC90ZD5cXG48L3RyPlxcbjx0cj5cXG48dGQ+U2FsdDwvdGQ+XFxuPHRkPjAuNmc8L3RkPlxcbjwvdHI+XFxuPC90Ym9keT5cXG48L3RhYmxlPjxoNSBjbGFzcz1cXFwicHJvZHVjdC1kZXRhaWwtdGl0bGVcXFwiPk1vcmU8L2g1PlxcbjxwPkRlbGljaW91cywgbnV0cml0aW91cyBhbmQgZ29vZCBmb3IgdGhlIHNvaWwsIGZhdmEgYmVhbnMgYXJlIGEgdmFyaWV0eSBvZiBicm9hZCBiZWFuLCBWaWNpYSBmYWJhLCBsZWZ0IHRvIHJpcGVuIGFuZCBkcnkgYmVmb3JlIGhhcnZlc3QuIFRoZXnigJlyZSBhbHNvIGtub3duIGFzIGZpZWxkIGJlYW5zLCBob3JzZSBiZWFucywgV2luZHNvciBiZWFucyBvciBmdWwuPC9wPlxcbjxwPlN1aXRhYmxlIGZvciB2ZWdhbnMgYW5kIHZlZ2V0YXJpYW5zPC9wPlxcblwiLFwiZGZjLWI6aGFzUXVhbnRpdHlcIjpcIl86YjU3XCIsXCJkZmMtYjppbWFnZVwiOlwiaHR0cHM6Ly9jZG4uc2hvcGlmeS5jb20vcy9maWxlcy8xLzA3MzEvODQ4My83OTM5L3Byb2R1Y3RzL1BhY2stQ2FuLUJha2VkLUJlYW5zLTE4MDB4Nl85ODN4NjU2XzUxMzc1OGU2LTI2MTYtNDY4Ny1hOGIyLWJhNmRkZTg2NDkyMy5qcGc/dj0xNjc3NzYwNzc4XCIsXCJkZmMtYjpuYW1lXCI6XCJCYWtlZCBCcml0aXNoIEJlYW5zIC0gQ2FzZSwgMTIgeCA0MDBnIChjYW4pXCIsXCJkZmMtYjpyZWZlcmVuY2VkQnlcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MTk0NjY1MDA0MDMvY2F0YWxvZ0l0ZW1cIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDY2NTAwNDAzL2NhdGFsb2dJdGVtXCIsXCJAdHlwZVwiOlwiZGZjLWI6Q2F0YWxvZ0l0ZW1cIixcImRmYy1iOm9mZmVyZWRUaHJvdWdoXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDY2NTAwNDAzL29mZmVyXCIsXCJkZmMtYjpza3VcIjpcIk5DQkIvQ0RcIixcImRmYy1iOnN0b2NrTGltaXRhdGlvblwiOlwiLTFcIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDY2NTAwNDAzL29mZmVyXCIsXCJAdHlwZVwiOlwiZGZjLWI6T2ZmZXJcIixcImRmYy1iOmhhc1ByaWNlXCI6e1wiQGlkXCI6XCJfOmI1OFwifX0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDY2NTMzMTcxXCIsXCJAdHlwZVwiOlwiZGZjLWI6U3VwcGxpZWRQcm9kdWN0XCIsXCJkZmMtYjpkZXNjcmlwdGlvblwiOlwiPHRhYmxlIHdpZHRoPVxcXCIxMDAlXFxcIj5cXG48dGJvZHk+XFxuPHRyIHN0eWxlPVxcXCJib3JkZXI6IDBweDtcXFwiPlxcbjx0ZCBiZ2NvbG9yPVxcXCIjZDZmYmVkXFxcIiBzdHlsZT1cXFwiY29sb3I6ICMwMDAwMDA7IGJvcmRlcjogMHB4O1xcXCI+PHN0cm9uZz5Tb3JyeSwgc3RhbmRhcmQgYmFybGV5IGZsYWtlcyBhcmUgbm8gbG9uZ2VyIGF2YWlsYWJsZSBidXQgb3VyIGRlbGljaW91cyAgT3JnYW5pYyBOYWtlZCBCYXJsZXkgRmxha2VzIGFyZSBiYWNrLjwvc3Ryb25nPjwvdGQ+XFxuPC90cj5cXG48L3Rib2R5PlxcbjwvdGFibGU+PHA+T3VyIHJpY2ggYW5kIG1hbHR5IGJhcmxleSBmbGFrZXMgYXJlIGEgc3RvcmUgY3VwYm9hcmQgc3RhcGxlLiBPcmdhbmljYWxseSBncm93biBhbmQgbWlsbGVkIGluIHRoZSBVSywgdGhleSBhZGQgdGV4dHVyZSB0byBmbGFwamFjayBhbmQgYmlzY3VpdCByZWNpcGVzLCBvciB0byBtYWtlIGEgaGVhcnRpZXIsIHJ1c3RpYyBwb3JyaWRnZSDigJMgdHJ5IGJsZW5kaW5nIHdpdGggb3VyIG90aGVyIGZsYWtlczwvcD5cXG48IS0tIHNwbGl0IC0tPjxoMz5Db21wbGV0ZSBQcm9kdWN0IERldGFpbHM8L2gzPjxsaSBpZD1cXFwidGFiMVxcXCIgY2xhc3M9XFxcImFjdGl2ZVxcXCI+XFxuPHA+QmFybGV5IGZsYWtlcyBhcmUgZ3JlYXQgYWRkZWQgdG8gbXVlc2xpIG9yIGdyYW5vbGEsIG9yIHVzZWQgaW4gYmFraW5nIGFzIGEgdG9wcGluZyBvciBtaXhlZCBpbnRvIGRvdWdoLiBFYXQgdGhlbSBhcyBhIGNlcmVhbCwgYmFrZSB3aXRoIHRoZW0sIG9yIGFkZCB0aGVtIHRvIHNvdXBzIGFuZCBzdGV3cyB0byB0aGlja2VuLCBib29zdCB0aGVpciBudXRyaXRpb24gYW5kIGFkZCBmbGF2b3VyLjwvcD5cXG48aDUgY2xhc3M9XFxcInByb2R1Y3QtZGV0YWlsLXRpdGxlXFxcIj5Db29raW5nIGluc3RydWN0aW9uczwvaDU+XFxuPHA+VG8gZWF0IGFzIGEgbXVlc2xpLCBjb21iaW5lIHdpdGggb3RoZXIgY2VyZWFsIGZsYWtlcyBhbmQgZW5qb3kuIE9yIHVzZSBhcyBhbiBvYXQgc3Vic3RpdHV0ZSBpbiBhbnkgYmFraW5nIHJlY2lwZS48L3A+XFxuPGg1IGNsYXNzPVxcXCJwcm9kdWN0LWRldGFpbC10aXRsZVxcXCI+SW5ncmVkaWVudHM8L2g1PlxcbjxwPjxiPkJhcmxlecKgPC9iPkZsYWtlczwvcD5cXG48aDUgY2xhc3M9XFxcInByb2R1Y3QtZGV0YWlsLXRpdGxlXFxcIj5BbGxlcmd5IGluZm9ybWF0aW9uPC9oNT5cXG48cD5Gb3IgYWxsZXJnZW5zLCBpbmNsdWRpbmcgY2VyZWFscyBjb250YWluaW5nIGdsdXRlbiwgc2VlIGluZ3JlZGllbnRzIGluIDxzdHJvbmc+Ym9sZDwvc3Ryb25nPjwvcD5cXG48dGFibGUgd2lkdGg9XFxcIjEwMCVcXFwiPlxcbjx0Ym9keT5cXG48dHI+XFxuPHRkPjxzdHJvbmc+VHlwaWNhbCB2YWx1ZXM8L3N0cm9uZz48L3RkPlxcbjx0ZD48c3Ryb25nPlBlciAxMDBnPC9zdHJvbmc+PC90ZD5cXG48L3RyPlxcbjx0cj5cXG48dGQ+RW5lcmd5PC90ZD5cXG48dGQ+MSw0MDFrSiAoMzMya2NhbCk8L3RkPlxcbjwvdHI+XFxuPHRyPlxcbjx0ZD5GYXQ8L3RkPlxcbjx0ZD4yLjFnPC90ZD5cXG48L3RyPlxcbjx0cj5cXG48dGQ+b2Ygd2hpY2ggc2F0dXJhdGVzPC90ZD5cXG48dGQ+MC4wZzwvdGQ+XFxuPC90cj5cXG48dHI+XFxuPHRkPkNhcmJvaHlkcmF0ZTwvdGQ+XFxuPHRkPjU4LjNnPC90ZD5cXG48L3RyPlxcbjx0cj5cXG48dGQ+b2Ygd2hpY2ggc3VnYXJzPC90ZD5cXG48dGQ+MS43ZzwvdGQ+XFxuPC90cj5cXG48dHI+XFxuPHRkPkZpYnJlPC90ZD5cXG48dGQ+MTcuM2c8L3RkPlxcbjwvdHI+XFxuPHRyPlxcbjx0ZD5Qcm90ZWluPC90ZD5cXG48dGQ+MTEuNGc8L3RkPlxcbjwvdHI+XFxuPHRyPlxcbjx0ZD5TYWx0PC90ZD5cXG48dGQ+MC4wZzwvdGQ+XFxuPC90cj5cXG48L3Rib2R5PlxcbjwvdGFibGU+XFxuPGg1IGNsYXNzPVxcXCJwcm9kdWN0LWRldGFpbC10aXRsZVxcXCI+TW9yZTwvaDU+XFxuPHVsPlxcbjxsaT5TdWl0YWJsZSBmb3IgdmVnYW5zIGFuZCB2ZWdldGFyaWFuc1xcbjwvbGk+XFxuPGxpPk5vIGFydGlmaWNpYWwgaW5ncmVkaWVudHNcXG48L2xpPlxcbjxsaT5HTSBmcmVlXFxuPC9saT5cXG48bGk+SGlnaCBGaWJyZVxcbjwvbGk+XFxuPC91bD5cXG48L2xpPlwiLFwiZGZjLWI6aGFzUXVhbnRpdHlcIjpcIl86YjQ3XCIsXCJkZmMtYjppbWFnZVwiOlwiaHR0cHM6Ly9jZG4uc2hvcGlmeS5jb20vcy9maWxlcy8xLzA3MzEvODQ4My83OTM5L3Byb2R1Y3RzL0JhcmxleS1GbGFrZXMtMjQwMHgxNjAwX2MxMjE0MDdjLTZmZDItNDZjYS1hMTI0LWRiNWRmOTQ0MjM2OC5qcGc/dj0xNjc3NzYwNzgxXCIsXCJkZmMtYjpuYW1lXCI6XCJCYXJsZXkgRmxha2VzLCBPcmdhbmljIC0gUmV0YWlsIHBhY2ssIDUwMGdcIixcImRmYy1iOnJlZmVyZW5jZWRCeVwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ2NjUzMzE3MS9jYXRhbG9nSXRlbVwifSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MTk0NjY1MzMxNzEvY2F0YWxvZ0l0ZW1cIixcIkB0eXBlXCI6XCJkZmMtYjpDYXRhbG9nSXRlbVwiLFwiZGZjLWI6b2ZmZXJlZFRocm91Z2hcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MTk0NjY1MzMxNzEvb2ZmZXJcIixcImRmYy1iOnNrdVwiOlwiT0tCQVI1XCIsXCJkZmMtYjpzdG9ja0xpbWl0YXRpb25cIjpcIi0xXCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ2NjUzMzE3MS9vZmZlclwiLFwiQHR5cGVcIjpcImRmYy1iOk9mZmVyXCIsXCJkZmMtYjpoYXNQcmljZVwiOntcIkBpZFwiOlwiXzpiNDhcIn19LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ2NjUzMzE3MS9wbGFubmVkQ29uc3VtcHRpb25GbG93XCIsXCJAdHlwZVwiOlwiZGZjLWI6QXNQbGFubmVkQ29uc3VtcHRpb25GbG93XCIsXCJkZmMtYjpjb25zdW1lc1wiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ2NjUzMzE3MVwiLFwiZGZjLWI6aGFzUXVhbnRpdHlcIjpcIl86Yjc1XCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ2NjUzMzE3MS9wbGFubmVkUHJvZHVjdGlvbkZsb3dcIixcIkB0eXBlXCI6XCJkZmMtYjpBc1BsYW5uZWRQcm9kdWN0aW9uRmxvd1wiLFwiZGZjLWI6aGFzUXVhbnRpdHlcIjpcIl86Yjc2XCIsXCJkZmMtYjpwcm9kdWNlc1wiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ2NjU2NTkzOVwifSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MTk0NjY1MzMxNzEvdHJhbnNmb3JtYXRpb25cIixcIkB0eXBlXCI6XCJkZmMtYjpBc1BsYW5uZWRUcmFuc2Zvcm1hdGlvblwiLFwiZGZjLWI6aGFzSW5jb21lXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDY2NTMzMTcxL3BsYW5uZWRDb25zdW1wdGlvbkZsb3dcIixcImRmYy1iOmhhc091dGNvbWVcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MTk0NjY1MzMxNzEvcGxhbm5lZFByb2R1Y3Rpb25GbG93XCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ2NjU2NTkzOVwiLFwiQHR5cGVcIjpcImRmYy1iOlN1cHBsaWVkUHJvZHVjdFwiLFwiZGZjLWI6ZGVzY3JpcHRpb25cIjpcIjx0YWJsZSB3aWR0aD1cXFwiMTAwJVxcXCI+XFxuPHRib2R5Plxcbjx0ciBzdHlsZT1cXFwiYm9yZGVyOiAwcHg7XFxcIj5cXG48dGQgYmdjb2xvcj1cXFwiI2Q2ZmJlZFxcXCIgc3R5bGU9XFxcImNvbG9yOiAjMDAwMDAwOyBib3JkZXI6IDBweDtcXFwiPjxzdHJvbmc+U29ycnksIHN0YW5kYXJkIGJhcmxleSBmbGFrZXMgYXJlIG5vIGxvbmdlciBhdmFpbGFibGUgYnV0IG91ciBkZWxpY2lvdXMgIE9yZ2FuaWMgTmFrZWQgQmFybGV5IEZsYWtlcyBhcmUgYmFjay48L3N0cm9uZz48L3RkPlxcbjwvdHI+XFxuPC90Ym9keT5cXG48L3RhYmxlPjxwPk91ciByaWNoIGFuZCBtYWx0eSBiYXJsZXkgZmxha2VzIGFyZSBhIHN0b3JlIGN1cGJvYXJkIHN0YXBsZS4gT3JnYW5pY2FsbHkgZ3Jvd24gYW5kIG1pbGxlZCBpbiB0aGUgVUssIHRoZXkgYWRkIHRleHR1cmUgdG8gZmxhcGphY2sgYW5kIGJpc2N1aXQgcmVjaXBlcywgb3IgdG8gbWFrZSBhIGhlYXJ0aWVyLCBydXN0aWMgcG9ycmlkZ2Ug4oCTIHRyeSBibGVuZGluZyB3aXRoIG91ciBvdGhlciBmbGFrZXM8L3A+XFxuPCEtLSBzcGxpdCAtLT48aDM+Q29tcGxldGUgUHJvZHVjdCBEZXRhaWxzPC9oMz48bGkgaWQ9XFxcInRhYjFcXFwiIGNsYXNzPVxcXCJhY3RpdmVcXFwiPlxcbjxwPkJhcmxleSBmbGFrZXMgYXJlIGdyZWF0IGFkZGVkIHRvIG11ZXNsaSBvciBncmFub2xhLCBvciB1c2VkIGluIGJha2luZyBhcyBhIHRvcHBpbmcgb3IgbWl4ZWQgaW50byBkb3VnaC4gRWF0IHRoZW0gYXMgYSBjZXJlYWwsIGJha2Ugd2l0aCB0aGVtLCBvciBhZGQgdGhlbSB0byBzb3VwcyBhbmQgc3Rld3MgdG8gdGhpY2tlbiwgYm9vc3QgdGhlaXIgbnV0cml0aW9uIGFuZCBhZGQgZmxhdm91ci48L3A+XFxuPGg1IGNsYXNzPVxcXCJwcm9kdWN0LWRldGFpbC10aXRsZVxcXCI+Q29va2luZyBpbnN0cnVjdGlvbnM8L2g1PlxcbjxwPlRvIGVhdCBhcyBhIG11ZXNsaSwgY29tYmluZSB3aXRoIG90aGVyIGNlcmVhbCBmbGFrZXMgYW5kIGVuam95LiBPciB1c2UgYXMgYW4gb2F0IHN1YnN0aXR1dGUgaW4gYW55IGJha2luZyByZWNpcGUuPC9wPlxcbjxoNSBjbGFzcz1cXFwicHJvZHVjdC1kZXRhaWwtdGl0bGVcXFwiPkluZ3JlZGllbnRzPC9oNT5cXG48cD48Yj5CYXJsZXnCoDwvYj5GbGFrZXM8L3A+XFxuPGg1IGNsYXNzPVxcXCJwcm9kdWN0LWRldGFpbC10aXRsZVxcXCI+QWxsZXJneSBpbmZvcm1hdGlvbjwvaDU+XFxuPHA+Rm9yIGFsbGVyZ2VucywgaW5jbHVkaW5nIGNlcmVhbHMgY29udGFpbmluZyBnbHV0ZW4sIHNlZSBpbmdyZWRpZW50cyBpbiA8c3Ryb25nPmJvbGQ8L3N0cm9uZz48L3A+XFxuPHRhYmxlIHdpZHRoPVxcXCIxMDAlXFxcIj5cXG48dGJvZHk+XFxuPHRyPlxcbjx0ZD48c3Ryb25nPlR5cGljYWwgdmFsdWVzPC9zdHJvbmc+PC90ZD5cXG48dGQ+PHN0cm9uZz5QZXIgMTAwZzwvc3Ryb25nPjwvdGQ+XFxuPC90cj5cXG48dHI+XFxuPHRkPkVuZXJneTwvdGQ+XFxuPHRkPjEsNDAxa0ogKDMzMmtjYWwpPC90ZD5cXG48L3RyPlxcbjx0cj5cXG48dGQ+RmF0PC90ZD5cXG48dGQ+Mi4xZzwvdGQ+XFxuPC90cj5cXG48dHI+XFxuPHRkPm9mIHdoaWNoIHNhdHVyYXRlczwvdGQ+XFxuPHRkPjAuMGc8L3RkPlxcbjwvdHI+XFxuPHRyPlxcbjx0ZD5DYXJib2h5ZHJhdGU8L3RkPlxcbjx0ZD41OC4zZzwvdGQ+XFxuPC90cj5cXG48dHI+XFxuPHRkPm9mIHdoaWNoIHN1Z2FyczwvdGQ+XFxuPHRkPjEuN2c8L3RkPlxcbjwvdHI+XFxuPHRyPlxcbjx0ZD5GaWJyZTwvdGQ+XFxuPHRkPjE3LjNnPC90ZD5cXG48L3RyPlxcbjx0cj5cXG48dGQ+UHJvdGVpbjwvdGQ+XFxuPHRkPjExLjRnPC90ZD5cXG48L3RyPlxcbjx0cj5cXG48dGQ+U2FsdDwvdGQ+XFxuPHRkPjAuMGc8L3RkPlxcbjwvdHI+XFxuPC90Ym9keT5cXG48L3RhYmxlPlxcbjxoNSBjbGFzcz1cXFwicHJvZHVjdC1kZXRhaWwtdGl0bGVcXFwiPk1vcmU8L2g1Plxcbjx1bD5cXG48bGk+U3VpdGFibGUgZm9yIHZlZ2FucyBhbmQgdmVnZXRhcmlhbnNcXG48L2xpPlxcbjxsaT5ObyBhcnRpZmljaWFsIGluZ3JlZGllbnRzXFxuPC9saT5cXG48bGk+R00gZnJlZVxcbjwvbGk+XFxuPGxpPkhpZ2ggRmlicmVcXG48L2xpPlxcbjwvdWw+XFxuPC9saT5cIixcImRmYy1iOmhhc1F1YW50aXR5XCI6XCJfOmI2MVwiLFwiZGZjLWI6aW1hZ2VcIjpcImh0dHBzOi8vY2RuLnNob3BpZnkuY29tL3MvZmlsZXMvMS8wNzMxLzg0ODMvNzkzOS9wcm9kdWN0cy9CYXJsZXktRmxha2VzLTI0MDB4MTYwMF9jMTIxNDA3Yy02ZmQyLTQ2Y2EtYTEyNC1kYjVkZjk0NDIzNjguanBnP3Y9MTY3Nzc2MDc4MVwiLFwiZGZjLWI6bmFtZVwiOlwiQmFybGV5IEZsYWtlcywgT3JnYW5pYyAtIFN0YW5kYXJkIGNhc2UsIDEwIHggNTAwZ1wiLFwiZGZjLWI6cmVmZXJlbmNlZEJ5XCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDY2NTY1OTM5L2NhdGFsb2dJdGVtXCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ2NjU2NTkzOS9jYXRhbG9nSXRlbVwiLFwiQHR5cGVcIjpcImRmYy1iOkNhdGFsb2dJdGVtXCIsXCJkZmMtYjpvZmZlcmVkVGhyb3VnaFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ2NjU2NTkzOS9vZmZlclwiLFwiZGZjLWI6c2t1XCI6XCJPS0JBQ1hcIixcImRmYy1iOnN0b2NrTGltaXRhdGlvblwiOlwiLTFcIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDY2NTY1OTM5L29mZmVyXCIsXCJAdHlwZVwiOlwiZGZjLWI6T2ZmZXJcIixcImRmYy1iOmhhc1ByaWNlXCI6e1wiQGlkXCI6XCJfOmI2MlwifX0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDczMzQ4OTE1XCIsXCJAdHlwZVwiOlwiZGZjLWI6U3VwcGxpZWRQcm9kdWN0XCIsXCJkZmMtYjpkZXNjcmlwdGlvblwiOlwiPHA+PHN0cm9uZz5GZXJtZW50ZWQgd2hvbGVncmFpbiBuYWtlZCBiYXJsZXkswqB0YXN0eSBhbmQgc3VjY3VsZW50wqBncmFpbnMgb2YgcmljaCBtYWx0eSB1bWFtaSBmbGF2b3VyLjwvc3Ryb25nPjwvcD5cXG48cD5UaGVzZSB3aG9sZSBmZXJtZW50ZWQgYmFybGV5wqBncmFpbnMgYXJlIHBhY2tlZCB3aXRoIGRlZXAgZmxhdm91ciBhbmQgbWFrZSBhIGRlbGljaW91cyBhZGRpdGlvbiB0byBicmVhZCwgcmlzb3R0bywgc3Rld3MsIHNhbGFkcyBhbmQgbW9yZS48L3A+XFxuPCEtLSBzcGxpdCAtLT48aDM+Q29tcGxldGUgUHJvZHVjdCBEZXRhaWxzPC9oMz5cXG48cD5BZGQgaW50ZW5zZWx5IGZsYXZvdXJlZCBtYWx0eSBhbmQgc3VjY3VsZW50IGdyYWluc8KgZnVsbCBvZiB1bWFtaSByaWNobmVzcyB0byBicmVhZHMsIHJpc290dG8sIHN0ZXdzLCBzb3VwcywgYW5kIGV2ZW4gc2FsYWRzLjwvcD5cXG48aDUgY2xhc3M9XFxcInByb2R1Y3QtZGV0YWlsLXRpdGxlXFxcIj5Db29raW5nIGluc3RydWN0aW9uczwvaDU+XFxuPHA+QWRkIGFib3V0IGhhbGYgYSB0ZWFzcG9vbiBwZXIgc2VydmluZyB0byBhbG1vc3QgYW55IGRpc2ggZm9yIGFkZGVkIGRlcHRoLCB1bWFtaSByaWNobmVzcyBhbmQgbWFsdHkgZmxhdm91ci48L3A+XFxuPGg1IGNsYXNzPVxcXCJwcm9kdWN0LWRldGFpbC10aXRsZVxcXCI+VG8gc3RvcmU8L2g1PlxcbjxwPktlZXAgcmVmcmlnZXJhdGVkIGFuZCB1c2Ugd2l0aGluIDQgd2Vla3Mgb2Ygb3BlbmluZy48L3A+XFxuPGg1IGNsYXNzPVxcXCJwcm9kdWN0LWRldGFpbC10aXRsZVxcXCI+SW5ncmVkaWVudHM8L2g1PlxcbjxwPk5ha2VkIDxzdHJvbmc+QmFybGV5PC9zdHJvbmc+LCBXYXRlciwgPHN0cm9uZz5XaGVhdDwvc3Ryb25nPiBGbG91ciwgU2FsdCwgTGl2ZSBDdWx0dXJlcyo8YnI+ICo8ZW0+TGFjdG9iYWNpbGx1cyBkZWxicnVlY2tpaTwvZW0+LCA8ZW0+QXNwZXJnaWxsdXMgc29qYWU8L2VtPiwgPGVtPlp5Z29zYWNjaGFyb215Y2VzIHJvdXhpaTwvZW0+PC9wPlxcbjxoNSBjbGFzcz1cXFwicHJvZHVjdC1kZXRhaWwtdGl0bGVcXFwiPkFsbGVyZ3kgaW5mb3JtYXRpb248L2g1PlxcbjxwPkNvbnRhaW5zIDxzdHJvbmc+QmFybGV5IChHbHV0ZW4pPC9zdHJvbmc+LDxzdHJvbmc+IFdoZWF0IChHbHV0ZW4pPC9zdHJvbmc+PC9wPlxcbjx0YWJsZSB3aWR0aD1cXFwiMTAwJVxcXCI+XFxuPHRib2R5Plxcbjx0cj5cXG48dGQ+PHN0cm9uZz5UeXBpY2FsIHZhbHVlczwvc3Ryb25nPjwvdGQ+XFxuPHRkPjxzdHJvbmc+UGVyIDEwMGc8L3N0cm9uZz48L3RkPlxcbjwvdHI+XFxuPHRyPlxcbjx0ZD5FbmVyZ3k8L3RkPlxcbjx0ZD41MDBrSiAoMTE5a2NhbCk8L3RkPlxcbjwvdHI+XFxuPHRyPlxcbjx0ZD5GYXQ8L3RkPlxcbjx0ZD4xLjdnPC90ZD5cXG48L3RyPlxcbjx0cj5cXG48dGQ+b2Ygd2hpY2ggc2F0dXJhdGVzPC90ZD5cXG48dGQ+MC41ZzwvdGQ+XFxuPC90cj5cXG48dHI+XFxuPHRkPkNhcmJvaHlkcmF0ZTwvdGQ+XFxuPHRkPjE5LjFnPC90ZD5cXG48L3RyPlxcbjx0cj5cXG48dGQ+b2Ygd2hpY2ggc3VnYXJzPC90ZD5cXG48dGQ+Mi43ZzwvdGQ+XFxuPC90cj5cXG48dHI+XFxuPHRkPkZpYnJlPC90ZD5cXG48dGQ+NS4yZzwvdGQ+XFxuPC90cj5cXG48dHI+XFxuPHRkPlByb3RlaW48L3RkPlxcbjx0ZD40LjJnPC90ZD5cXG48L3RyPlxcbjx0cj5cXG48dGQ+U2FsdDwvdGQ+XFxuPHRkPjguNWc8L3RkPlxcbjwvdHI+XFxuPC90Ym9keT5cXG48L3RhYmxlPjxoNSBjbGFzcz1cXFwicHJvZHVjdC1kZXRhaWwtdGl0bGVcXFwiPk1vcmU8L2g1PlxcbjxwPk91ciBGZXJtZW50ZWQgV2hvbGVncmFpbiBOYWtlZCBCYXJsZXkgaXMgZmVybWVudGVkIGluIHRoZSBzYW1lIHdheSBhcyBtYW55IHNveWEgZmVybWVudHMgdXNlZCBmb3IgYmxhY2sgYmVhbnMsIGJ1dCB1c2luZ8KgbmFrZWQgYmFybGV5IGdyYWluIGluc3RlYWQgb2Ygc295IGJlYW5zLiBJdCdzIG1hZGUgd2l0aCBqdXN0IG5ha2VkIGJhcmxleSBncmFpbnMsIHdhdGVyIGFuZCBzYWx0LCBmZXJtZW50ZWQgd2l0aCBhIGxpdmUgY3VsdHVyZSBvZiA8ZW0+TGFjdG9iYWNpbGx1cyBkZWxicnVlY2tpaTwvZW0+LCA8ZW0+QXNwZXJnaWxsdXMgc29qYWU8L2VtPiBhbmQgPGVtPlp5Z29zYWNjaGFyb215Y2VzIHJvdXhpaTwvZW0+LjwvcD5cXG48cD5TdWl0YWJsZSBmb3IgdmVnYW5zLjwvcD5cXG48cD5QYWNrZWQgaW4gcmVjeWNsYWJsZSBnbGFzcyBqYXIgd2l0aCBtZXRhbCBsaWQuPC9wPlwiLFwiZGZjLWI6aGFzUXVhbnRpdHlcIjpcIl86YjUxXCIsXCJkZmMtYjppbWFnZVwiOlwiaHR0cHM6Ly9jZG4uc2hvcGlmeS5jb20vcy9maWxlcy8xLzA3MzEvODQ4My83OTM5L3Byb2R1Y3RzL0Zlcm1lbnRlZC1XaG9sZWdyYWluLU5ha2VkLUJhcmxleS1TcG9vbi0xNjAweDEwMDBfZDZmZWEwOTItZmRlNC00YTk4LWJlYzgtYmIzY2EwYTFmZDRkLmpwZz92PTE2Nzc3NjA4NjBcIixcImRmYy1iOm5hbWVcIjpcIkZlcm1lbnRlZCBOYWtlZCBCYXJsZXkgLSBSZXRhaWwgamFyLCAxNzVnIChqYXIpXCIsXCJkZmMtYjpyZWZlcmVuY2VkQnlcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MTk0NzMzNDg5MTUvY2F0YWxvZ0l0ZW1cIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDczMzQ4OTE1L2NhdGFsb2dJdGVtXCIsXCJAdHlwZVwiOlwiZGZjLWI6Q2F0YWxvZ0l0ZW1cIixcImRmYy1iOm9mZmVyZWRUaHJvdWdoXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDczMzQ4OTE1L29mZmVyXCIsXCJkZmMtYjpza3VcIjpcIk5NTkIvSkZcIixcImRmYy1iOnN0b2NrTGltaXRhdGlvblwiOlwiLTFcIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDczMzQ4OTE1L29mZmVyXCIsXCJAdHlwZVwiOlwiZGZjLWI6T2ZmZXJcIixcImRmYy1iOmhhc1ByaWNlXCI6e1wiQGlkXCI6XCJfOmI1MlwifX0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDczMzQ4OTE1L3BsYW5uZWRDb25zdW1wdGlvbkZsb3dcIixcIkB0eXBlXCI6XCJkZmMtYjpBc1BsYW5uZWRDb25zdW1wdGlvbkZsb3dcIixcImRmYy1iOmNvbnN1bWVzXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDczMzQ4OTE1XCIsXCJkZmMtYjpoYXNRdWFudGl0eVwiOlwiXzpiNzlcIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDczMzQ4OTE1L3BsYW5uZWRQcm9kdWN0aW9uRmxvd1wiLFwiQHR5cGVcIjpcImRmYy1iOkFzUGxhbm5lZFByb2R1Y3Rpb25GbG93XCIsXCJkZmMtYjpoYXNRdWFudGl0eVwiOlwiXzpiODBcIixcImRmYy1iOnByb2R1Y2VzXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDczMzgxNjgzXCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ3MzM0ODkxNS90cmFuc2Zvcm1hdGlvblwiLFwiQHR5cGVcIjpcImRmYy1iOkFzUGxhbm5lZFRyYW5zZm9ybWF0aW9uXCIsXCJkZmMtYjpoYXNJbmNvbWVcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MTk0NzMzNDg5MTUvcGxhbm5lZENvbnN1bXB0aW9uRmxvd1wiLFwiZGZjLWI6aGFzT3V0Y29tZVwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ3MzM0ODkxNS9wbGFubmVkUHJvZHVjdGlvbkZsb3dcIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDczMzgxNjgzXCIsXCJAdHlwZVwiOlwiZGZjLWI6U3VwcGxpZWRQcm9kdWN0XCIsXCJkZmMtYjpkZXNjcmlwdGlvblwiOlwiPHA+PHN0cm9uZz5GZXJtZW50ZWQgd2hvbGVncmFpbiBuYWtlZCBiYXJsZXkswqB0YXN0eSBhbmQgc3VjY3VsZW50wqBncmFpbnMgb2YgcmljaCBtYWx0eSB1bWFtaSBmbGF2b3VyLjwvc3Ryb25nPjwvcD5cXG48cD5UaGVzZSB3aG9sZSBmZXJtZW50ZWQgYmFybGV5wqBncmFpbnMgYXJlIHBhY2tlZCB3aXRoIGRlZXAgZmxhdm91ciBhbmQgbWFrZSBhIGRlbGljaW91cyBhZGRpdGlvbiB0byBicmVhZCwgcmlzb3R0bywgc3Rld3MsIHNhbGFkcyBhbmQgbW9yZS48L3A+XFxuPCEtLSBzcGxpdCAtLT48aDM+Q29tcGxldGUgUHJvZHVjdCBEZXRhaWxzPC9oMz5cXG48cD5BZGQgaW50ZW5zZWx5IGZsYXZvdXJlZCBtYWx0eSBhbmQgc3VjY3VsZW50IGdyYWluc8KgZnVsbCBvZiB1bWFtaSByaWNobmVzcyB0byBicmVhZHMsIHJpc290dG8sIHN0ZXdzLCBzb3VwcywgYW5kIGV2ZW4gc2FsYWRzLjwvcD5cXG48aDUgY2xhc3M9XFxcInByb2R1Y3QtZGV0YWlsLXRpdGxlXFxcIj5Db29raW5nIGluc3RydWN0aW9uczwvaDU+XFxuPHA+QWRkIGFib3V0IGhhbGYgYSB0ZWFzcG9vbiBwZXIgc2VydmluZyB0byBhbG1vc3QgYW55IGRpc2ggZm9yIGFkZGVkIGRlcHRoLCB1bWFtaSByaWNobmVzcyBhbmQgbWFsdHkgZmxhdm91ci48L3A+XFxuPGg1IGNsYXNzPVxcXCJwcm9kdWN0LWRldGFpbC10aXRsZVxcXCI+VG8gc3RvcmU8L2g1PlxcbjxwPktlZXAgcmVmcmlnZXJhdGVkIGFuZCB1c2Ugd2l0aGluIDQgd2Vla3Mgb2Ygb3BlbmluZy48L3A+XFxuPGg1IGNsYXNzPVxcXCJwcm9kdWN0LWRldGFpbC10aXRsZVxcXCI+SW5ncmVkaWVudHM8L2g1PlxcbjxwPk5ha2VkIDxzdHJvbmc+QmFybGV5PC9zdHJvbmc+LCBXYXRlciwgPHN0cm9uZz5XaGVhdDwvc3Ryb25nPiBGbG91ciwgU2FsdCwgTGl2ZSBDdWx0dXJlcyo8YnI+ICo8ZW0+TGFjdG9iYWNpbGx1cyBkZWxicnVlY2tpaTwvZW0+LCA8ZW0+QXNwZXJnaWxsdXMgc29qYWU8L2VtPiwgPGVtPlp5Z29zYWNjaGFyb215Y2VzIHJvdXhpaTwvZW0+PC9wPlxcbjxoNSBjbGFzcz1cXFwicHJvZHVjdC1kZXRhaWwtdGl0bGVcXFwiPkFsbGVyZ3kgaW5mb3JtYXRpb248L2g1PlxcbjxwPkNvbnRhaW5zIDxzdHJvbmc+QmFybGV5IChHbHV0ZW4pPC9zdHJvbmc+LDxzdHJvbmc+IFdoZWF0IChHbHV0ZW4pPC9zdHJvbmc+PC9wPlxcbjx0YWJsZSB3aWR0aD1cXFwiMTAwJVxcXCI+XFxuPHRib2R5Plxcbjx0cj5cXG48dGQ+PHN0cm9uZz5UeXBpY2FsIHZhbHVlczwvc3Ryb25nPjwvdGQ+XFxuPHRkPjxzdHJvbmc+UGVyIDEwMGc8L3N0cm9uZz48L3RkPlxcbjwvdHI+XFxuPHRyPlxcbjx0ZD5FbmVyZ3k8L3RkPlxcbjx0ZD41MDBrSiAoMTE5a2NhbCk8L3RkPlxcbjwvdHI+XFxuPHRyPlxcbjx0ZD5GYXQ8L3RkPlxcbjx0ZD4xLjdnPC90ZD5cXG48L3RyPlxcbjx0cj5cXG48dGQ+b2Ygd2hpY2ggc2F0dXJhdGVzPC90ZD5cXG48dGQ+MC41ZzwvdGQ+XFxuPC90cj5cXG48dHI+XFxuPHRkPkNhcmJvaHlkcmF0ZTwvdGQ+XFxuPHRkPjE5LjFnPC90ZD5cXG48L3RyPlxcbjx0cj5cXG48dGQ+b2Ygd2hpY2ggc3VnYXJzPC90ZD5cXG48dGQ+Mi43ZzwvdGQ+XFxuPC90cj5cXG48dHI+XFxuPHRkPkZpYnJlPC90ZD5cXG48dGQ+NS4yZzwvdGQ+XFxuPC90cj5cXG48dHI+XFxuPHRkPlByb3RlaW48L3RkPlxcbjx0ZD40LjJnPC90ZD5cXG48L3RyPlxcbjx0cj5cXG48dGQ+U2FsdDwvdGQ+XFxuPHRkPjguNWc8L3RkPlxcbjwvdHI+XFxuPC90Ym9keT5cXG48L3RhYmxlPjxoNSBjbGFzcz1cXFwicHJvZHVjdC1kZXRhaWwtdGl0bGVcXFwiPk1vcmU8L2g1PlxcbjxwPk91ciBGZXJtZW50ZWQgV2hvbGVncmFpbiBOYWtlZCBCYXJsZXkgaXMgZmVybWVudGVkIGluIHRoZSBzYW1lIHdheSBhcyBtYW55IHNveWEgZmVybWVudHMgdXNlZCBmb3IgYmxhY2sgYmVhbnMsIGJ1dCB1c2luZ8KgbmFrZWQgYmFybGV5IGdyYWluIGluc3RlYWQgb2Ygc295IGJlYW5zLiBJdCdzIG1hZGUgd2l0aCBqdXN0IG5ha2VkIGJhcmxleSBncmFpbnMsIHdhdGVyIGFuZCBzYWx0LCBmZXJtZW50ZWQgd2l0aCBhIGxpdmUgY3VsdHVyZSBvZiA8ZW0+TGFjdG9iYWNpbGx1cyBkZWxicnVlY2tpaTwvZW0+LCA8ZW0+QXNwZXJnaWxsdXMgc29qYWU8L2VtPiBhbmQgPGVtPlp5Z29zYWNjaGFyb215Y2VzIHJvdXhpaTwvZW0+LjwvcD5cXG48cD5TdWl0YWJsZSBmb3IgdmVnYW5zLjwvcD5cXG48cD5QYWNrZWQgaW4gcmVjeWNsYWJsZSBnbGFzcyBqYXIgd2l0aCBtZXRhbCBsaWQuPC9wPlwiLFwiZGZjLWI6aGFzUXVhbnRpdHlcIjpcIl86YjY1XCIsXCJkZmMtYjppbWFnZVwiOlwiaHR0cHM6Ly9jZG4uc2hvcGlmeS5jb20vcy9maWxlcy8xLzA3MzEvODQ4My83OTM5L3Byb2R1Y3RzL0Zlcm1lbnRlZC1XaG9sZWdyYWluLU5ha2VkLUJhcmxleS1TcG9vbi0xNjAweDEwMDBfZDZmZWEwOTItZmRlNC00YTk4LWJlYzgtYmIzY2EwYTFmZDRkLmpwZz92PTE2Nzc3NjA4NjBcIixcImRmYy1iOm5hbWVcIjpcIkZlcm1lbnRlZCBOYWtlZCBCYXJsZXkgLSBDYXNlLCA2IHggMTc1ZyAoamFyKVwiLFwiZGZjLWI6cmVmZXJlbmNlZEJ5XCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTE5NDczMzgxNjgzL2NhdGFsb2dJdGVtXCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ3MzM4MTY4My9jYXRhbG9nSXRlbVwiLFwiQHR5cGVcIjpcImRmYy1iOkNhdGFsb2dJdGVtXCIsXCJkZmMtYjpvZmZlcmVkVGhyb3VnaFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ3MzM4MTY4My9vZmZlclwiLFwiZGZjLWI6c2t1XCI6XCJOTU5CL0M2XCIsXCJkZmMtYjpzdG9ja0xpbWl0YXRpb25cIjpcIi0xXCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUxOTQ3MzM4MTY4My9vZmZlclwiLFwiQHR5cGVcIjpcImRmYy1iOk9mZmVyXCIsXCJkZmMtYjpoYXNQcmljZVwiOntcIkBpZFwiOlwiXzpiNjZcIn19LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTYyNzI0NDg1MVwiLFwiQHR5cGVcIjpcImRmYy1iOlN1cHBsaWVkUHJvZHVjdFwiLFwiZGZjLWI6aGFzUXVhbnRpdHlcIjpcIl86YjU5XCIsXCJkZmMtYjppbWFnZVwiOlwiaHR0cHM6Ly9jZG4uc2hvcGlmeS5jb20vcy9maWxlcy8xLzA3MzEvODQ4My83OTM5L3Byb2R1Y3RzL1BhY2stQ2FuLUJha2VkLUJlYW5zLTE4MDB4Nl9hNGQ1ODQ1OS1iZjUyLTQ4YTktYmFlNy04MDdmNDAzNWI4N2YuanBnP3Y9MTY3Nzc2MDc3N1wiLFwiZGZjLWI6bmFtZVwiOlwiQmFrZWQgQnJpdGlzaCBCZWFucyAoVG9MKSAtIENhc2UgLSAxMiB4IDQwMGcgY2Fuc1wiLFwiZGZjLWI6cmVmZXJlbmNlZEJ5XCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjI3MjQ0ODUxL2NhdGFsb2dJdGVtXCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTYyNzI0NDg1MS9jYXRhbG9nSXRlbVwiLFwiQHR5cGVcIjpcImRmYy1iOkNhdGFsb2dJdGVtXCIsXCJkZmMtYjpvZmZlcmVkVGhyb3VnaFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTYyNzI0NDg1MS9vZmZlclwiLFwiZGZjLWI6c2t1XCI6XCJOQ0JCQ0RcIixcImRmYy1iOnN0b2NrTGltaXRhdGlvblwiOlwiLTFcIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjI3MjQ0ODUxL29mZmVyXCIsXCJAdHlwZVwiOlwiZGZjLWI6T2ZmZXJcIixcImRmYy1iOmhhc1ByaWNlXCI6e1wiQGlkXCI6XCJfOmI2MFwifX0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjI3Mjc3NjE5XCIsXCJAdHlwZVwiOlwiZGZjLWI6U3VwcGxpZWRQcm9kdWN0XCIsXCJkZmMtYjpoYXNRdWFudGl0eVwiOlwiXzpiNDVcIixcImRmYy1iOmltYWdlXCI6XCJodHRwczovL2Nkbi5zaG9waWZ5LmNvbS9zL2ZpbGVzLzEvMDczMS84NDgzLzc5MzkvcHJvZHVjdHMvUGFjay1DYW4tQmFrZWQtQmVhbnMtMTgwMHg2X2E0ZDU4NDU5LWJmNTItNDhhOS1iYWU3LTgwN2Y0MDM1Yjg3Zi5qcGc/dj0xNjc3NzYwNzc3XCIsXCJkZmMtYjpuYW1lXCI6XCJCYWtlZCBCcml0aXNoIEJlYW5zIChUb0wpIC0gU2luZ2xlIC0gNDAwZyBjYW5cIixcImRmYy1iOnJlZmVyZW5jZWRCeVwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTYyNzI3NzYxOS9jYXRhbG9nSXRlbVwifSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2MjcyNzc2MTkvY2F0YWxvZ0l0ZW1cIixcIkB0eXBlXCI6XCJkZmMtYjpDYXRhbG9nSXRlbVwiLFwiZGZjLWI6b2ZmZXJlZFRocm91Z2hcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2MjcyNzc2MTkvb2ZmZXJcIixcImRmYy1iOnNrdVwiOlwiTkNCQlQ0XCIsXCJkZmMtYjpzdG9ja0xpbWl0YXRpb25cIjpcIjIwXCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTYyNzI3NzYxOS9vZmZlclwiLFwiQHR5cGVcIjpcImRmYy1iOk9mZmVyXCIsXCJkZmMtYjpoYXNQcmljZVwiOntcIkBpZFwiOlwiXzpiNDZcIn19LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTYyNzI3NzYxOS9wbGFubmVkQ29uc3VtcHRpb25GbG93XCIsXCJAdHlwZVwiOlwiZGZjLWI6QXNQbGFubmVkQ29uc3VtcHRpb25GbG93XCIsXCJkZmMtYjpjb25zdW1lc1wiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTYyNzI3NzYxOVwiLFwiZGZjLWI6aGFzUXVhbnRpdHlcIjpcIl86YjczXCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTYyNzI3NzYxOS9wbGFubmVkUHJvZHVjdGlvbkZsb3dcIixcIkB0eXBlXCI6XCJkZmMtYjpBc1BsYW5uZWRQcm9kdWN0aW9uRmxvd1wiLFwiZGZjLWI6aGFzUXVhbnRpdHlcIjpcIl86Yjc0XCIsXCJkZmMtYjpwcm9kdWNlc1wiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTYyNzI0NDg1MVwifSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2MjcyNzc2MTkvdHJhbnNmb3JtYXRpb25cIixcIkB0eXBlXCI6XCJkZmMtYjpBc1BsYW5uZWRUcmFuc2Zvcm1hdGlvblwiLFwiZGZjLWI6aGFzSW5jb21lXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjI3Mjc3NjE5L3BsYW5uZWRDb25zdW1wdGlvbkZsb3dcIixcImRmYy1iOmhhc091dGNvbWVcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2MjcyNzc2MTkvcGxhbm5lZFByb2R1Y3Rpb25GbG93XCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTYyODc4NDk0N1wiLFwiQHR5cGVcIjpcImRmYy1iOlN1cHBsaWVkUHJvZHVjdFwiLFwiZGZjLWI6aGFzUXVhbnRpdHlcIjpcIl86YjY3XCIsXCJkZmMtYjppbWFnZVwiOlwiaHR0cHM6Ly9jZG4uc2hvcGlmeS5jb20vcy9maWxlcy8xLzA3MzEvODQ4My83OTM5L3Byb2R1Y3RzL1dob2xlLUZhdmEtQmVhbnMtT3JnYW5pYy1DYW5uZWRfZmNiNjRmZDctOGNhMy00NjVhLThmNTYtNDQzY2YyOGUwYjcxLmpwZz92PTE2Nzc3NjA5NzdcIixcImRmYy1iOm5hbWVcIjpcIk9yZ2FuaWMgV2hvbGUgRmF2YSBCZWFucyBpbiBXYXRlciAoVG9MKSAtIENhc2UgLSAxMiB4IDQwMGcgY2Fuc1wiLFwiZGZjLWI6cmVmZXJlbmNlZEJ5XCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjI4Nzg0OTQ3L2NhdGFsb2dJdGVtXCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTYyODc4NDk0Ny9jYXRhbG9nSXRlbVwiLFwiQHR5cGVcIjpcImRmYy1iOkNhdGFsb2dJdGVtXCIsXCJkZmMtYjpvZmZlcmVkVGhyb3VnaFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTYyODc4NDk0Ny9vZmZlclwiLFwiZGZjLWI6c2t1XCI6XCJPQ0ZCQ0RcIixcImRmYy1iOnN0b2NrTGltaXRhdGlvblwiOlwiLTFcIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjI4Nzg0OTQ3L29mZmVyXCIsXCJAdHlwZVwiOlwiZGZjLWI6T2ZmZXJcIixcImRmYy1iOmhhc1ByaWNlXCI6e1wiQGlkXCI6XCJfOmI2OFwifX0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjI4ODE3NzE1XCIsXCJAdHlwZVwiOlwiZGZjLWI6U3VwcGxpZWRQcm9kdWN0XCIsXCJkZmMtYjpoYXNRdWFudGl0eVwiOlwiXzpiNTNcIixcImRmYy1iOmltYWdlXCI6XCJodHRwczovL2Nkbi5zaG9waWZ5LmNvbS9zL2ZpbGVzLzEvMDczMS84NDgzLzc5MzkvcHJvZHVjdHMvV2hvbGUtRmF2YS1CZWFucy1PcmdhbmljLUNhbm5lZF9mY2I2NGZkNy04Y2EzLTQ2NWEtOGY1Ni00NDNjZjI4ZTBiNzEuanBnP3Y9MTY3Nzc2MDk3N1wiLFwiZGZjLWI6bmFtZVwiOlwiT3JnYW5pYyBXaG9sZSBGYXZhIEJlYW5zIGluIFdhdGVyIChUb0wpIC0gU2luZ2xlIC0gNDAwZyBjYW5cIixcImRmYy1iOnJlZmVyZW5jZWRCeVwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTYyODgxNzcxNS9jYXRhbG9nSXRlbVwifSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2Mjg4MTc3MTUvY2F0YWxvZ0l0ZW1cIixcIkB0eXBlXCI6XCJkZmMtYjpDYXRhbG9nSXRlbVwiLFwiZGZjLWI6b2ZmZXJlZFRocm91Z2hcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2Mjg4MTc3MTUvb2ZmZXJcIixcImRmYy1iOnNrdVwiOlwiT0NGQlQ0XCIsXCJkZmMtYjpzdG9ja0xpbWl0YXRpb25cIjpcIi0xXCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTYyODgxNzcxNS9vZmZlclwiLFwiQHR5cGVcIjpcImRmYy1iOk9mZmVyXCIsXCJkZmMtYjpoYXNQcmljZVwiOntcIkBpZFwiOlwiXzpiNTRcIn19LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTYyODgxNzcxNS9wbGFubmVkQ29uc3VtcHRpb25GbG93XCIsXCJAdHlwZVwiOlwiZGZjLWI6QXNQbGFubmVkQ29uc3VtcHRpb25GbG93XCIsXCJkZmMtYjpjb25zdW1lc1wiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTYyODgxNzcxNVwiLFwiZGZjLWI6aGFzUXVhbnRpdHlcIjpcIl86YjgxXCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTYyODgxNzcxNS9wbGFubmVkUHJvZHVjdGlvbkZsb3dcIixcIkB0eXBlXCI6XCJkZmMtYjpBc1BsYW5uZWRQcm9kdWN0aW9uRmxvd1wiLFwiZGZjLWI6aGFzUXVhbnRpdHlcIjpcIl86YjgyXCIsXCJkZmMtYjpwcm9kdWNlc1wiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTYyODc4NDk0N1wifSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2Mjg4MTc3MTUvdHJhbnNmb3JtYXRpb25cIixcIkB0eXBlXCI6XCJkZmMtYjpBc1BsYW5uZWRUcmFuc2Zvcm1hdGlvblwiLFwiZGZjLWI6aGFzSW5jb21lXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjI4ODE3NzE1L3BsYW5uZWRDb25zdW1wdGlvbkZsb3dcIixcImRmYy1iOmhhc091dGNvbWVcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2Mjg4MTc3MTUvcGxhbm5lZFByb2R1Y3Rpb25GbG93XCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTY1NDA0OTA3NVwiLFwiQHR5cGVcIjpcImRmYy1iOlN1cHBsaWVkUHJvZHVjdFwiLFwiZGZjLWI6aGFzUXVhbnRpdHlcIjpcIl86YjQ5XCIsXCJkZmMtYjpuYW1lXCI6XCJDYXJsaW4gUGVhcyBpbiBXYXRlciwgT3JnYW5pYyAoRElTVFJJQlVUT1IpIC0gUmV0YWlsIGNhbiAoNDAwZyBjYW4pXCIsXCJkZmMtYjpyZWZlcmVuY2VkQnlcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2NTQwNDkwNzUvY2F0YWxvZ0l0ZW1cIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjU0MDQ5MDc1L2NhdGFsb2dJdGVtXCIsXCJAdHlwZVwiOlwiZGZjLWI6Q2F0YWxvZ0l0ZW1cIixcImRmYy1iOm9mZmVyZWRUaHJvdWdoXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjU0MDQ5MDc1L29mZmVyXCIsXCJkZmMtYjpza3VcIjpcIk9DQ1BUNFwiLFwiZGZjLWI6c3RvY2tMaW1pdGF0aW9uXCI6XCItMVwifSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2NTQwNDkwNzUvb2ZmZXJcIixcIkB0eXBlXCI6XCJkZmMtYjpPZmZlclwiLFwiZGZjLWI6aGFzUHJpY2VcIjp7XCJAaWRcIjpcIl86YjUwXCJ9fSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2NTQwNDkwNzUvcGxhbm5lZENvbnN1bXB0aW9uRmxvd1wiLFwiQHR5cGVcIjpcImRmYy1iOkFzUGxhbm5lZENvbnN1bXB0aW9uRmxvd1wiLFwiZGZjLWI6Y29uc3VtZXNcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2NTQwNDkwNzVcIixcImRmYy1iOmhhc1F1YW50aXR5XCI6XCJfOmI3N1wifSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2NTQwNDkwNzUvcGxhbm5lZFByb2R1Y3Rpb25GbG93XCIsXCJAdHlwZVwiOlwiZGZjLWI6QXNQbGFubmVkUHJvZHVjdGlvbkZsb3dcIixcImRmYy1iOmhhc1F1YW50aXR5XCI6XCJfOmI3OFwiLFwiZGZjLWI6cHJvZHVjZXNcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2NTQwODE4NDNcIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjU0MDQ5MDc1L3RyYW5zZm9ybWF0aW9uXCIsXCJAdHlwZVwiOlwiZGZjLWI6QXNQbGFubmVkVHJhbnNmb3JtYXRpb25cIixcImRmYy1iOmhhc0luY29tZVwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTY1NDA0OTA3NS9wbGFubmVkQ29uc3VtcHRpb25GbG93XCIsXCJkZmMtYjpoYXNPdXRjb21lXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjU0MDQ5MDc1L3BsYW5uZWRQcm9kdWN0aW9uRmxvd1wifSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2NTQwODE4NDNcIixcIkB0eXBlXCI6XCJkZmMtYjpTdXBwbGllZFByb2R1Y3RcIixcImRmYy1iOmhhc1F1YW50aXR5XCI6XCJfOmI2M1wiLFwiZGZjLWI6bmFtZVwiOlwiQ2FybGluIFBlYXMgaW4gV2F0ZXIsIE9yZ2FuaWMgKERJU1RSSUJVVE9SKSAtIFN0YW5kYXJkIGNhc2UgKDEyIHggNDAwZyBjYW4pXCIsXCJkZmMtYjpyZWZlcmVuY2VkQnlcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2NTQwODE4NDMvY2F0YWxvZ0l0ZW1cIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjU0MDgxODQzL2NhdGFsb2dJdGVtXCIsXCJAdHlwZVwiOlwiZGZjLWI6Q2F0YWxvZ0l0ZW1cIixcImRmYy1iOm9mZmVyZWRUaHJvdWdoXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjU0MDgxODQzL29mZmVyXCIsXCJkZmMtYjpza3VcIjpcIk9DQ1BDRFwiLFwiZGZjLWI6c3RvY2tMaW1pdGF0aW9uXCI6XCItMVwifSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2NTQwODE4NDMvb2ZmZXJcIixcIkB0eXBlXCI6XCJkZmMtYjpPZmZlclwiLFwiZGZjLWI6aGFzUHJpY2VcIjp7XCJAaWRcIjpcIl86YjY0XCJ9fSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2NjM1ODQ1NjNcIixcIkB0eXBlXCI6XCJkZmMtYjpTdXBwbGllZFByb2R1Y3RcIixcImRmYy1iOmhhc1F1YW50aXR5XCI6XCJfOmI1NVwiLFwiZGZjLWI6bmFtZVwiOlwiUm9hc3RlZCBGYXZhIEJlYW5zLCBMaWdodGx5IFNlYSBTYWx0ZWQgKERJU1RSSUJVVE9SKSAtIFJldGFpbCBwYWNrICgzMDBnKVwiLFwiZGZjLWI6cmVmZXJlbmNlZEJ5XCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjYzNTg0NTYzL2NhdGFsb2dJdGVtXCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTY2MzU4NDU2My9jYXRhbG9nSXRlbVwiLFwiQHR5cGVcIjpcImRmYy1iOkNhdGFsb2dJdGVtXCIsXCJkZmMtYjpvZmZlcmVkVGhyb3VnaFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTY2MzU4NDU2My9vZmZlclwiLFwiZGZjLWI6c2t1XCI6XCJOUkZTUjNcIixcImRmYy1iOnN0b2NrTGltaXRhdGlvblwiOlwiLTFcIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjYzNTg0NTYzL29mZmVyXCIsXCJAdHlwZVwiOlwiZGZjLWI6T2ZmZXJcIixcImRmYy1iOmhhc1ByaWNlXCI6e1wiQGlkXCI6XCJfOmI1NlwifX0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjYzNTg0NTYzL3BsYW5uZWRDb25zdW1wdGlvbkZsb3dcIixcIkB0eXBlXCI6XCJkZmMtYjpBc1BsYW5uZWRDb25zdW1wdGlvbkZsb3dcIixcImRmYy1iOmNvbnN1bWVzXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjYzNTg0NTYzXCIsXCJkZmMtYjpoYXNRdWFudGl0eVwiOlwiXzpiODNcIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjYzNTg0NTYzL3BsYW5uZWRQcm9kdWN0aW9uRmxvd1wiLFwiQHR5cGVcIjpcImRmYy1iOkFzUGxhbm5lZFByb2R1Y3Rpb25GbG93XCIsXCJkZmMtYjpoYXNRdWFudGl0eVwiOlwiXzpiODRcIixcImRmYy1iOnByb2R1Y2VzXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjYzNjE3MzMxXCJ9LHtcIkBpZFwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTY2MzU4NDU2My90cmFuc2Zvcm1hdGlvblwiLFwiQHR5cGVcIjpcImRmYy1iOkFzUGxhbm5lZFRyYW5zZm9ybWF0aW9uXCIsXCJkZmMtYjpoYXNJbmNvbWVcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2NjM1ODQ1NjMvcGxhbm5lZENvbnN1bXB0aW9uRmxvd1wiLFwiZGZjLWI6aGFzT3V0Y29tZVwiOlwiaHR0cHM6Ly9mb29kLWRhdGEtY29sbGFib3JhdGlvbi1wcm9kdWMtZmU4NzAxNTJmNjM0Lmhlcm9rdWFwcC5jb20vcHJvZHVjdC80NDUyNTY2MzU4NDU2My9wbGFubmVkUHJvZHVjdGlvbkZsb3dcIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjYzNjE3MzMxXCIsXCJAdHlwZVwiOlwiZGZjLWI6U3VwcGxpZWRQcm9kdWN0XCIsXCJkZmMtYjpoYXNRdWFudGl0eVwiOlwiXzpiNjlcIixcImRmYy1iOm5hbWVcIjpcIlJvYXN0ZWQgRmF2YSBCZWFucywgTGlnaHRseSBTZWEgU2FsdGVkIChESVNUUklCVVRPUikgLSBTdGFuZGFyZCBjYXNlICgxMCB4IDMwMGcpXCIsXCJkZmMtYjpyZWZlcmVuY2VkQnlcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2NjM2MTczMzEvY2F0YWxvZ0l0ZW1cIn0se1wiQGlkXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjYzNjE3MzMxL2NhdGFsb2dJdGVtXCIsXCJAdHlwZVwiOlwiZGZjLWI6Q2F0YWxvZ0l0ZW1cIixcImRmYy1iOm9mZmVyZWRUaHJvdWdoXCI6XCJodHRwczovL2Zvb2QtZGF0YS1jb2xsYWJvcmF0aW9uLXByb2R1Yy1mZTg3MDE1MmY2MzQuaGVyb2t1YXBwLmNvbS9wcm9kdWN0LzQ0NTI1NjYzNjE3MzMxL29mZmVyXCIsXCJkZmMtYjpza3VcIjpcIk5SRlNDWFwiLFwiZGZjLWI6c3RvY2tMaW1pdGF0aW9uXCI6XCItMVwifSx7XCJAaWRcIjpcImh0dHBzOi8vZm9vZC1kYXRhLWNvbGxhYm9yYXRpb24tcHJvZHVjLWZlODcwMTUyZjYzNC5oZXJva3VhcHAuY29tL3Byb2R1Y3QvNDQ1MjU2NjM2MTczMzEvb2ZmZXJcIixcIkB0eXBlXCI6XCJkZmMtYjpPZmZlclwiLFwiZGZjLWI6aGFzUHJpY2VcIjp7XCJAaWRcIjpcIl86YjcwXCJ9fV19IiwibGFzdElkIjoiODE0NzI5MjI1ODYxMSIsInJlbWFpbmluZ1Byb2R1Y3RzQ291bnRBZnRlciI6MCwic3VjY2VzcyI6dHJ1ZSwibWVzc2FnZSI6IlByb2R1Y3RzIHJldHJpZXZlZCBzdWNjZXNzZnVsbHkifQ== - recorded_at: Fri, 24 May 2024 01:48:50 GMT + eyJAY29udGV4dCI6Imh0dHBzOi8vd3d3LmRhdGFmb29kY29uc29ydGl1bS5vcmciLCJAZ3JhcGgiOlt7IkBpZCI6Il86YjExMyIsIkB0eXBlIjoiZGZjLWI6UXVhbnRpdGF0aXZlVmFsdWUiLCJkZmMtYjpoYXNVbml0IjoiZGZjLW06S2lsb2dyYW0iLCJkZmMtYjp2YWx1ZSI6IjAuMDQifSx7IkBpZCI6Il86YjExNCIsIkB0eXBlIjoiZGZjLWI6UHJpY2UiLCJkZmMtYjpWQVRyYXRlIjoiMCIsImRmYy1iOmhhc1VuaXQiOiJkZmMtbTpFdXJvIiwiZGZjLWI6dmFsdWUiOiI2LjI1In0seyJAaWQiOiJfOmIxMTUiLCJAdHlwZSI6ImRmYy1iOlF1YW50aXRhdGl2ZVZhbHVlIiwiZGZjLWI6aGFzVW5pdCI6ImRmYy1tOktpbG9ncmFtIiwiZGZjLWI6dmFsdWUiOiIwLjI1In0seyJAaWQiOiJfOmIxMTYiLCJAdHlwZSI6ImRmYy1iOlByaWNlIiwiZGZjLWI6VkFUcmF0ZSI6IjAiLCJkZmMtYjpoYXNVbml0IjoiZGZjLW06RXVybyIsImRmYy1iOnZhbHVlIjoiNy4wMCJ9LHsiQGlkIjoiXzpiMTE3IiwiQHR5cGUiOiJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZSIsImRmYy1iOmhhc1VuaXQiOiJkZmMtbTpLaWxvZ3JhbSIsImRmYy1iOnZhbHVlIjoiMC4yNCJ9LHsiQGlkIjoiXzpiMTE4IiwiQHR5cGUiOiJkZmMtYjpQcmljZSIsImRmYy1iOlZBVHJhdGUiOiIwIiwiZGZjLWI6aGFzVW5pdCI6ImRmYy1tOkV1cm8iLCJkZmMtYjp2YWx1ZSI6IjMwLjIwIn0seyJAaWQiOiJfOmIxMTkiLCJAdHlwZSI6ImRmYy1iOlF1YW50aXRhdGl2ZVZhbHVlIiwiZGZjLWI6aGFzVW5pdCI6ImRmYy1tOktpbG9ncmFtIiwiZGZjLWI6dmFsdWUiOiIxLjUifSx7IkBpZCI6Il86YjEyMCIsIkB0eXBlIjoiZGZjLWI6UHJpY2UiLCJkZmMtYjpWQVRyYXRlIjoiMCIsImRmYy1iOmhhc1VuaXQiOiJkZmMtbTpFdXJvIiwiZGZjLWI6dmFsdWUiOiI3OS4wMCJ9LHsiQGlkIjoiXzpiMTIxIiwiQHR5cGUiOiJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZSIsImRmYy1iOmhhc1VuaXQiOiJkZmMtbTpQaWVjZSIsImRmYy1iOnZhbHVlIjoiNiJ9LHsiQGlkIjoiXzpiMTIyIiwiQHR5cGUiOiJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZSIsImRmYy1iOmhhc1VuaXQiOiJkZmMtbTpQaWVjZSIsImRmYy1iOnZhbHVlIjoiMSJ9LHsiQGlkIjoiXzpiMTIzIiwiQHR5cGUiOiJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZSIsImRmYy1iOmhhc1VuaXQiOiJkZmMtbTpQaWVjZSIsImRmYy1iOnZhbHVlIjoiNiJ9LHsiQGlkIjoiXzpiMTI0IiwiQHR5cGUiOiJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZSIsImRmYy1iOmhhc1VuaXQiOiJkZmMtbTpQaWVjZSIsImRmYy1iOnZhbHVlIjoiMSJ9LHsiQGlkIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2MjM4MjU5IiwiQHR5cGUiOiJkZmMtYjpTdXBwbGllZFByb2R1Y3QiLCJkZmMtYjpkZXNjcmlwdGlvbiI6Ijx0YWJsZSB3aWR0aD1cIjEwMCVcIj5cbjx0Ym9keT5cbjx0ciBzdHlsZT1cImJvcmRlcjogMHB4O1wiIGRhdGEtbWNlLXN0eWxlPVwiYm9yZGVyOiAwcHg7XCI+XG48dGQgYmdjb2xvcj1cIiNkNmZiZWRcIiBzdHlsZT1cImNvbG9yOiAjMDAwMDAwOyBib3JkZXI6IDBweDsgd2lkdGg6IDUyNnB4O1wiIGRhdGEtbWNlLXN0eWxlPVwiY29sb3I6ICMwMDAwMDA7IGJvcmRlcjogMHB4OyB3aWR0aDogNTI2cHg7XCI+PGI+VGhpcyByaWNoLCBpbnRlbnNlIGFuZCBkZWVwbHkgZmxhdm91cmVkIDYteWVhciBvbGQgYXBwbGUgYmFsc2FtaWMgdmluZWdhciBpcyBtYWRlIHVzaW5nIHRoZSB0cmFkaXRpb25hbCBJdGFsaWFuIG1ldGhvZCBvZiByZWR1Y3Rpb24gYW5kIGNvbmNlbnRyYXRpb24gb2YgdGhlIGp1aWNlIG92ZXIgYSBsZW5ndGh5IHBlcmlvZCBvZiB0aW1lLCByYXRoZXIgdGhhbiBieSBhZGRpbmcgZmxhdm91cmluZyBhbmQgY29sb3VyaW5nLsKgPC9iPjwvdGQ+XG48L3RyPlxuPC90Ym9keT5cbjwvdGFibGU+XG48cD5MaWJlcnR5IEZpZWxkcyBwcm9kdWNlIHNtYWxsIGJhdGNoZXMgb2Ygc3VwZXJiIHN5cnVwLCBiYWxzYW1pYyB2aW5lZ2FyLCBjaWRlciBhbmQgdm9ka2EgYnkgaGFuZCBmcm9tIHRoZSBmcnVpdCBvZiB0aGVpciBvd24gRG9yc2V0IGFwcGxlIG9yY2hhcmRzLCBwbGFudGVkIGZyb20gMjAxMC48YnI+PC9wPlxuPHA+VGhlIGJhbHNhbWljIHZpbmVnYXIgaXMgYWdlZCBmb3IgNiB5ZWFycyBpbiBiYXJyZWxzLsKgVGhlIG9ubHkgaW5ncmVkaWVudCBpcyBhcHBsZXMuPC9wPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5Ib3cgdG8gdXNlPC9oNT5cbjxwPlVzZSBsaWtlIEl0YWxpYW4gYmFsc2FtaWMgdmluZWdhci7CoDxzcGFuIGRhdGEtbWNlLWZyYWdtZW50PVwiMVwiPkFzIHdlbGwgYXMgdXNpbmcgb24gc2FsYWRzLCBpdOKAmXMgYSBncmVhdCBwYXJ0bmVyIGZvciBncmlsbGVkIG1lYXRzIG9yIGNoYXJjdXRlcmllOyBhIGRyb3AgYnJpbmdzIG91dCB0aGUgdGFzdGUgb2Ygc3RyYXdiZXJyaWVzIGFuZCBvdGhlciBzb2Z0IGZydWl0czsgYW5kIGl0IGNhbiByZWFsbHkgZW5oYW5jZSBhIHN0ZXcsIHNhdWNlIG9yIGEgc291cC7CoDwvc3Bhbj48L3A+XG48aDUgY2xhc3M9XCJwcm9kdWN0LWRldGFpbC10aXRsZVwiPlRvIHN0b3JlPGJyPlxuPC9oNT5cbjxwPkZvciBiZXN0IGJlZm9yZSBkYXRlIHNlZSBwYWNrLiBTdG9yZSBpbiBhIGNvb2wsIGRyeSBwbGFjZS48YnI+PC9wPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5JbmdyZWRpZW50czwvaDU+XG48cD5BcHBsZXM8YnI+PC9wPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5BbGxlcmd5IGluZm9ybWF0aW9uPC9oNT5cbjxwPk5vIGFsbGVyZ2Vucy48L3A+PGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5Nb3JlPC9oNT5cbjxwPlByb2R1Y3Qgb2bCoERvcnNldDxicj5TdWl0YWJsZSBmb3IgdmVnYW5zIGFuZCB2ZWdldGFyaWFuczxicj48L3A+IiwiZGZjLWI6aGFzUXVhbnRpdHkiOiJfOmIxMTMiLCJkZmMtYjppbWFnZSI6Imh0dHBzOi8vY2RuLnNob3BpZnkuY29tL3MvZmlsZXMvMS8wNzMxLzg0ODMvNzkzOS9wcm9kdWN0cy9MaWJlcnR5LUZpZWxkcy1BcHBsZS1CYWxzYW1pYy1WaW5lZ2FyLTQwbWxfNzk2MTdlZWEtYWI4Yy00MDcwLTllNGQtNzExYmYwMzBhZDA3LmpwZz92PTE2Nzc3NjA3NzIiLCJkZmMtYjpuYW1lIjoiQXBwbGUgQmFsc2FtaWMgVmluZWdhciAtIFJldGFpbCBib3R0bGUsIDQwbWwiLCJkZmMtYjpyZWZlcmVuY2VkQnkiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjYyMzgyNTkvQ2F0YWxvZ0l0ZW0ifSx7IkBpZCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjIzODI1OS9Bc1BsYW5uZWRDb25zdW1wdGlvbkZsb3ciLCJAdHlwZSI6ImRmYy1iOkFzUGxhbm5lZENvbnN1bXB0aW9uRmxvdyIsImRmYy1iOmNvbnN1bWVzIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2MjM4MjU5IiwiZGZjLWI6aGFzUXVhbnRpdHkiOiJfOmIxMjEifSx7IkBpZCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjIzODI1OS9Bc1BsYW5uZWRQcm9kdWN0aW9uRmxvdyIsIkB0eXBlIjoiZGZjLWI6QXNQbGFubmVkUHJvZHVjdGlvbkZsb3ciLCJkZmMtYjpoYXNRdWFudGl0eSI6Il86YjEyMiIsImRmYy1iOnByb2R1Y2VzIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2MjcxMDI3In0seyJAaWQiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjYyMzgyNTkvQXNQbGFubmVkVHJhbnNmb3JtYXRpb24iLCJAdHlwZSI6ImRmYy1iOkFzUGxhbm5lZFRyYW5zZm9ybWF0aW9uIiwiZGZjLWI6aGFzSW5jb21lIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2MjM4MjU5L0FzUGxhbm5lZENvbnN1bXB0aW9uRmxvdyIsImRmYy1iOmhhc091dGNvbWUiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjYyMzgyNTkvQXNQbGFubmVkUHJvZHVjdGlvbkZsb3cifSx7IkBpZCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjIzODI1OS9DYXRhbG9nSXRlbSIsIkB0eXBlIjoiZGZjLWI6Q2F0YWxvZ0l0ZW0iLCJkZmMtYjpvZmZlcmVkVGhyb3VnaCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjIzODI1OS9PZmZlciIsImRmYy1iOnNrdSI6IkxJQi9OQUJWSS9CRiIsImRmYy1iOnN0b2NrTGltaXRhdGlvbiI6Ii0xIn0seyJAaWQiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjYyMzgyNTkvT2ZmZXIiLCJAdHlwZSI6ImRmYy1iOk9mZmVyIiwiZGZjLWI6aGFzUHJpY2UiOnsiQGlkIjoiXzpiMTE0In19LHsiQGlkIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2MjcxMDI3IiwiQHR5cGUiOiJkZmMtYjpTdXBwbGllZFByb2R1Y3QiLCJkZmMtYjpkZXNjcmlwdGlvbiI6Ijx0YWJsZSB3aWR0aD1cIjEwMCVcIj5cbjx0Ym9keT5cbjx0ciBzdHlsZT1cImJvcmRlcjogMHB4O1wiIGRhdGEtbWNlLXN0eWxlPVwiYm9yZGVyOiAwcHg7XCI+XG48dGQgYmdjb2xvcj1cIiNkNmZiZWRcIiBzdHlsZT1cImNvbG9yOiAjMDAwMDAwOyBib3JkZXI6IDBweDsgd2lkdGg6IDUyNnB4O1wiIGRhdGEtbWNlLXN0eWxlPVwiY29sb3I6ICMwMDAwMDA7IGJvcmRlcjogMHB4OyB3aWR0aDogNTI2cHg7XCI+PGI+VGhpcyByaWNoLCBpbnRlbnNlIGFuZCBkZWVwbHkgZmxhdm91cmVkIDYteWVhciBvbGQgYXBwbGUgYmFsc2FtaWMgdmluZWdhciBpcyBtYWRlIHVzaW5nIHRoZSB0cmFkaXRpb25hbCBJdGFsaWFuIG1ldGhvZCBvZiByZWR1Y3Rpb24gYW5kIGNvbmNlbnRyYXRpb24gb2YgdGhlIGp1aWNlIG92ZXIgYSBsZW5ndGh5IHBlcmlvZCBvZiB0aW1lLCByYXRoZXIgdGhhbiBieSBhZGRpbmcgZmxhdm91cmluZyBhbmQgY29sb3VyaW5nLsKgPC9iPjwvdGQ+XG48L3RyPlxuPC90Ym9keT5cbjwvdGFibGU+XG48cD5MaWJlcnR5IEZpZWxkcyBwcm9kdWNlIHNtYWxsIGJhdGNoZXMgb2Ygc3VwZXJiIHN5cnVwLCBiYWxzYW1pYyB2aW5lZ2FyLCBjaWRlciBhbmQgdm9ka2EgYnkgaGFuZCBmcm9tIHRoZSBmcnVpdCBvZiB0aGVpciBvd24gRG9yc2V0IGFwcGxlIG9yY2hhcmRzLCBwbGFudGVkIGZyb20gMjAxMC48YnI+PC9wPlxuPHA+VGhlIGJhbHNhbWljIHZpbmVnYXIgaXMgYWdlZCBmb3IgNiB5ZWFycyBpbiBiYXJyZWxzLsKgVGhlIG9ubHkgaW5ncmVkaWVudCBpcyBhcHBsZXMuPC9wPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5Ib3cgdG8gdXNlPC9oNT5cbjxwPlVzZSBsaWtlIEl0YWxpYW4gYmFsc2FtaWMgdmluZWdhci7CoDxzcGFuIGRhdGEtbWNlLWZyYWdtZW50PVwiMVwiPkFzIHdlbGwgYXMgdXNpbmcgb24gc2FsYWRzLCBpdOKAmXMgYSBncmVhdCBwYXJ0bmVyIGZvciBncmlsbGVkIG1lYXRzIG9yIGNoYXJjdXRlcmllOyBhIGRyb3AgYnJpbmdzIG91dCB0aGUgdGFzdGUgb2Ygc3RyYXdiZXJyaWVzIGFuZCBvdGhlciBzb2Z0IGZydWl0czsgYW5kIGl0IGNhbiByZWFsbHkgZW5oYW5jZSBhIHN0ZXcsIHNhdWNlIG9yIGEgc291cC7CoDwvc3Bhbj48L3A+XG48aDUgY2xhc3M9XCJwcm9kdWN0LWRldGFpbC10aXRsZVwiPlRvIHN0b3JlPGJyPlxuPC9oNT5cbjxwPkZvciBiZXN0IGJlZm9yZSBkYXRlIHNlZSBwYWNrLiBTdG9yZSBpbiBhIGNvb2wsIGRyeSBwbGFjZS48YnI+PC9wPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5JbmdyZWRpZW50czwvaDU+XG48cD5BcHBsZXM8YnI+PC9wPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5BbGxlcmd5IGluZm9ybWF0aW9uPC9oNT5cbjxwPk5vIGFsbGVyZ2Vucy48L3A+PGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5Nb3JlPC9oNT5cbjxwPlByb2R1Y3Qgb2bCoERvcnNldDxicj5TdWl0YWJsZSBmb3IgdmVnYW5zIGFuZCB2ZWdldGFyaWFuczxicj48L3A+IiwiZGZjLWI6aGFzUXVhbnRpdHkiOiJfOmIxMTciLCJkZmMtYjppbWFnZSI6Imh0dHBzOi8vY2RuLnNob3BpZnkuY29tL3MvZmlsZXMvMS8wNzMxLzg0ODMvNzkzOS9wcm9kdWN0cy9MaWJlcnR5LUZpZWxkcy1BcHBsZS1CYWxzYW1pYy1WaW5lZ2FyLTQwbWxfNzk2MTdlZWEtYWI4Yy00MDcwLTllNGQtNzExYmYwMzBhZDA3LmpwZz92PTE2Nzc3NjA3NzIiLCJkZmMtYjpuYW1lIjoiQXBwbGUgQmFsc2FtaWMgVmluZWdhciAtIENhc2UsIDYgeCA0MG1sIiwiZGZjLWI6cmVmZXJlbmNlZEJ5IjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2MjcxMDI3L0NhdGFsb2dJdGVtIn0seyJAaWQiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjYyNzEwMjcvQ2F0YWxvZ0l0ZW0iLCJAdHlwZSI6ImRmYy1iOkNhdGFsb2dJdGVtIiwiZGZjLWI6b2ZmZXJlZFRocm91Z2giOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjYyNzEwMjcvT2ZmZXIiLCJkZmMtYjpza3UiOiJMSUIvTkFCVkkvQzYiLCJkZmMtYjpzdG9ja0xpbWl0YXRpb24iOiI1NSJ9LHsiQGlkIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2MjcxMDI3L09mZmVyIiwiQHR5cGUiOiJkZmMtYjpPZmZlciIsImRmYy1iOmhhc1ByaWNlIjp7IkBpZCI6Il86YjExOCJ9fSx7IkBpZCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjM2OTMzMSIsIkB0eXBlIjoiZGZjLWI6U3VwcGxpZWRQcm9kdWN0IiwiZGZjLWI6ZGVzY3JpcHRpb24iOiI8dGFibGUgd2lkdGg9XCIxMDAlXCI+XG48dGJvZHk+XG48dHIgc3R5bGU9XCJib3JkZXI6IDBweDtcIiBkYXRhLW1jZS1zdHlsZT1cImJvcmRlcjogMHB4O1wiPlxuPHRkIHN0eWxlPVwiY29sb3I6ICMwMDAwMDA7IGJvcmRlcjogMHB4OyB3aWR0aDogNTI2cHg7XCIgYmdjb2xvcj1cIiNkNmZiZWRcIiBkYXRhLW1jZS1zdHlsZT1cImNvbG9yOiAjMDAwMDAwOyBib3JkZXI6IDBweDsgd2lkdGg6IDUyNnB4O1wiPjxiPkRvcnNldCdzIGFuc3dlciB0byBtYXBsZSBzeXJ1cCwgTGliZXJ0eSBGaWVsZHMnIEFwcGxlIFN5cnVwIGlzIGEgbHV4dXJpb3VzbHkgcmljaCBhbmQgaW50ZW5zZSBuYXR1cmFsIHN3ZWV0ZW5lciwgbWFkZSBvbmx5IGZyb20gdGhlIGFwcGxlcyBvZiB0aGVpciBvcmNoYXJkcy48L2I+PC90ZD5cbjwvdHI+XG48L3Rib2R5PlxuPC90YWJsZT5cbjxwPkxpYmVydHkgRmllbGRzIHByb2R1Y2Ugc21hbGwgYmF0Y2hlcyBvZiBzdXBlcmIgc3lydXAsIGJhbHNhbWljIHZpbmVnYXIsIGNpZGVyIGFuZCB2b2RrYSBieSBoYW5kIGZyb20gdGhlIGZydWl0IG9mIHRoZWlyIG93biBEb3JzZXQgYXBwbGUgb3JjaGFyZHMsIHBsYW50ZWQgZnJvbSAyMDEwLjxicj48L3A+XG48cD5UaGUgc3lydXAgY29udGFpbnMgbm8gYWRkaXRpdmVzLCBjaGVtaWNhbHMsIGZpbmluZyBhZ2VudHMgb3Igc3VnYXIuIE92ZXIgMmtnIGFwcGxlcyBnbyBpbnRvIGVhY2ggYm90dGxlLjwvcD5cbjxsaSBpZD1cInRhYjFcIiBjbGFzcz1cImFjdGl2ZVwiPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5Ib3cgdG8gdXNlPC9oNT5cbjxwPlVzZSBhcyBhIHN3ZWV0ZW5lciwgbGlrZSBtYXBsZSBzeXJ1cC7CoERlbGljaW91cyB3aXRoIHBhbmNha2VzLCB5b2dodXJ0IHBvcnJpZGdlLCBpY2UgY3JlYW0sIEZyZW5jaCB0b2FzdCBhbmQgbW9yZS4gTWl4IHdpdGggc3BhcmtsaW5nIHdhdGVyIG9yIGxlbW9uYWRlIHRvIG1ha2UgYSBub24tYWxjb2hvbGljIHN1bW1lciBjdXAsIG9yIHVzZSBhcyBhIHN3ZWV0ZW5lciBpbiBjb29raW5nLjwvcD5cbjxoNSBjbGFzcz1cInByb2R1Y3QtZGV0YWlsLXRpdGxlXCI+VG8gc3RvcmU8YnI+XG48L2g1PlxuPHA+Rm9yIGJlc3QgYmVmb3JlIGRhdGUgc2VlIHBhY2suIFN0b3JlIGluIGEgY29vbCwgZHJ5IHBsYWNlLiBSZWZyaWdlcmF0ZSBhZnRlciBvcGVuaW5nIGFuZCB1c2Ugd2l0aGluIDYgd2Vla3M8YnI+PC9wPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5JbmdyZWRpZW50czwvaDU+XG48cD5BcHBsZXM8YnI+PC9wPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5BbGxlcmd5IGluZm9ybWF0aW9uPC9oNT5cbjxwPk5vIGFsbGVyZ2Vucy4gRnJlZSBmcm9tIHN1bHBoaXRlcy48L3A+XG48dGFibGUgd2lkdGg9XCIxMDAlXCI+XG48dGJvZHk+XG48dHI+XG48dGQ+PHN0cm9uZz5UeXBpY2FsIHZhbHVlczwvc3Ryb25nPjwvdGQ+XG48dGQ+PHN0cm9uZz5QZXIgMTAwZzwvc3Ryb25nPjwvdGQ+XG48L3RyPlxuPHRyPlxuPHRkPkVuZXJneTwvdGQ+XG48dGQ+MTA3MWtKICgyNTJrY2FsKTwvdGQ+XG48L3RyPlxuPHRyPlxuPHRkPkZhdDwvdGQ+XG48dGQ+MC4wZzwvdGQ+XG48L3RyPlxuPHRyPlxuPHRkPm9mIHdoaWNoIHNhdHVyYXRlczwvdGQ+XG48dGQ+MC4wZzwvdGQ+XG48L3RyPlxuPHRyPlxuPHRkPkNhcmJvaHlkcmF0ZTwvdGQ+XG48dGQ+NjIuOWc8L3RkPlxuPC90cj5cbjx0cj5cbjx0ZD5vZiB3aGljaCBzdWdhcnMqPC90ZD5cbjx0ZD41Mi44ZzwvdGQ+XG48L3RyPlxuPHRyPlxuPHRkPlByb3RlaW48L3RkPlxuPHRkPjAuMWc8L3RkPlxuPC90cj5cbjx0cj5cbjx0ZD5TYWx0PC90ZD5cbjx0ZD4wLjBnPC90ZD5cbjwvdHI+XG48L3Rib2R5PlxuPC90YWJsZT5cbjxoNSBjbGFzcz1cInByb2R1Y3QtZGV0YWlsLXRpdGxlXCI+TW9yZTwvaDU+XG48cD5Qcm9kdWN0IG9mwqBEb3JzZXQ8YnI+U3VpdGFibGUgZm9yIHZlZ2FucyBhbmQgdmVnZXRhcmlhbnM8YnI+PC9wPlxuPC9saT4iLCJkZmMtYjpoYXNRdWFudGl0eSI6Il86YjExNSIsImRmYy1iOmltYWdlIjoiaHR0cHM6Ly9jZG4uc2hvcGlmeS5jb20vcy9maWxlcy8xLzA3MzEvODQ4My83OTM5L3Byb2R1Y3RzL0xpYmVydHktRmllbGRzLUFwcGxlLVN5cnVwLTIwMG1sX2IxZDFlOGNjLTk1MzAtNDBmZS05NmQ4LTVjNTJmNmRhOGEwMC5qcGc/dj0xNjc3NzYwNzc0IiwiZGZjLWI6bmFtZSI6IkFwcGxlIFN5cnVwIC0gUmV0YWlsIGJvdHRsZSwgMjUwbWwiLCJkZmMtYjpyZWZlcmVuY2VkQnkiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjYzNjkzMzEvQ2F0YWxvZ0l0ZW0ifSx7IkBpZCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjM2OTMzMS9Bc1BsYW5uZWRDb25zdW1wdGlvbkZsb3ciLCJAdHlwZSI6ImRmYy1iOkFzUGxhbm5lZENvbnN1bXB0aW9uRmxvdyIsImRmYy1iOmNvbnN1bWVzIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2MzY5MzMxIiwiZGZjLWI6aGFzUXVhbnRpdHkiOiJfOmIxMjMifSx7IkBpZCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjM2OTMzMS9Bc1BsYW5uZWRQcm9kdWN0aW9uRmxvdyIsIkB0eXBlIjoiZGZjLWI6QXNQbGFubmVkUHJvZHVjdGlvbkZsb3ciLCJkZmMtYjpoYXNRdWFudGl0eSI6Il86YjEyNCIsImRmYy1iOnByb2R1Y2VzIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2NDAyMDk5In0seyJAaWQiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjYzNjkzMzEvQXNQbGFubmVkVHJhbnNmb3JtYXRpb24iLCJAdHlwZSI6ImRmYy1iOkFzUGxhbm5lZFRyYW5zZm9ybWF0aW9uIiwiZGZjLWI6aGFzSW5jb21lIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2MzY5MzMxL0FzUGxhbm5lZENvbnN1bXB0aW9uRmxvdyIsImRmYy1iOmhhc091dGNvbWUiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjYzNjkzMzEvQXNQbGFubmVkUHJvZHVjdGlvbkZsb3cifSx7IkBpZCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjM2OTMzMS9DYXRhbG9nSXRlbSIsIkB0eXBlIjoiZGZjLWI6Q2F0YWxvZ0l0ZW0iLCJkZmMtYjpvZmZlcmVkVGhyb3VnaCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjM2OTMzMS9PZmZlciIsImRmYy1iOnNrdSI6IkxJQi9OQVNZUi9CVCIsImRmYy1iOnN0b2NrTGltaXRhdGlvbiI6Ii0xIn0seyJAaWQiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjYzNjkzMzEvT2ZmZXIiLCJAdHlwZSI6ImRmYy1iOk9mZmVyIiwiZGZjLWI6aGFzUHJpY2UiOnsiQGlkIjoiXzpiMTE2In19LHsiQGlkIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2NDAyMDk5IiwiQHR5cGUiOiJkZmMtYjpTdXBwbGllZFByb2R1Y3QiLCJkZmMtYjpkZXNjcmlwdGlvbiI6Ijx0YWJsZSB3aWR0aD1cIjEwMCVcIj5cbjx0Ym9keT5cbjx0ciBzdHlsZT1cImJvcmRlcjogMHB4O1wiIGRhdGEtbWNlLXN0eWxlPVwiYm9yZGVyOiAwcHg7XCI+XG48dGQgc3R5bGU9XCJjb2xvcjogIzAwMDAwMDsgYm9yZGVyOiAwcHg7IHdpZHRoOiA1MjZweDtcIiBiZ2NvbG9yPVwiI2Q2ZmJlZFwiIGRhdGEtbWNlLXN0eWxlPVwiY29sb3I6ICMwMDAwMDA7IGJvcmRlcjogMHB4OyB3aWR0aDogNTI2cHg7XCI+PGI+RG9yc2V0J3MgYW5zd2VyIHRvIG1hcGxlIHN5cnVwLCBMaWJlcnR5IEZpZWxkcycgQXBwbGUgU3lydXAgaXMgYSBsdXh1cmlvdXNseSByaWNoIGFuZCBpbnRlbnNlIG5hdHVyYWwgc3dlZXRlbmVyLCBtYWRlIG9ubHkgZnJvbSB0aGUgYXBwbGVzIG9mIHRoZWlyIG9yY2hhcmRzLjwvYj48L3RkPlxuPC90cj5cbjwvdGJvZHk+XG48L3RhYmxlPlxuPHA+TGliZXJ0eSBGaWVsZHMgcHJvZHVjZSBzbWFsbCBiYXRjaGVzIG9mIHN1cGVyYiBzeXJ1cCwgYmFsc2FtaWMgdmluZWdhciwgY2lkZXIgYW5kIHZvZGthIGJ5IGhhbmQgZnJvbSB0aGUgZnJ1aXQgb2YgdGhlaXIgb3duIERvcnNldCBhcHBsZSBvcmNoYXJkcywgcGxhbnRlZCBmcm9tIDIwMTAuPGJyPjwvcD5cbjxwPlRoZSBzeXJ1cCBjb250YWlucyBubyBhZGRpdGl2ZXMsIGNoZW1pY2FscywgZmluaW5nIGFnZW50cyBvciBzdWdhci4gT3ZlciAya2cgYXBwbGVzIGdvIGludG8gZWFjaCBib3R0bGUuPC9wPlxuPGxpIGlkPVwidGFiMVwiIGNsYXNzPVwiYWN0aXZlXCI+XG48aDUgY2xhc3M9XCJwcm9kdWN0LWRldGFpbC10aXRsZVwiPkhvdyB0byB1c2U8L2g1PlxuPHA+VXNlIGFzIGEgc3dlZXRlbmVyLCBsaWtlIG1hcGxlIHN5cnVwLsKgRGVsaWNpb3VzIHdpdGggcGFuY2FrZXMsIHlvZ2h1cnQgcG9ycmlkZ2UsIGljZSBjcmVhbSwgRnJlbmNoIHRvYXN0IGFuZCBtb3JlLiBNaXggd2l0aCBzcGFya2xpbmcgd2F0ZXIgb3IgbGVtb25hZGUgdG8gbWFrZSBhIG5vbi1hbGNvaG9saWMgc3VtbWVyIGN1cCwgb3IgdXNlIGFzIGEgc3dlZXRlbmVyIGluIGNvb2tpbmcuPC9wPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5UbyBzdG9yZTxicj5cbjwvaDU+XG48cD5Gb3IgYmVzdCBiZWZvcmUgZGF0ZSBzZWUgcGFjay4gU3RvcmUgaW4gYSBjb29sLCBkcnkgcGxhY2UuIFJlZnJpZ2VyYXRlIGFmdGVyIG9wZW5pbmcgYW5kIHVzZSB3aXRoaW4gNiB3ZWVrczxicj48L3A+XG48aDUgY2xhc3M9XCJwcm9kdWN0LWRldGFpbC10aXRsZVwiPkluZ3JlZGllbnRzPC9oNT5cbjxwPkFwcGxlczxicj48L3A+XG48aDUgY2xhc3M9XCJwcm9kdWN0LWRldGFpbC10aXRsZVwiPkFsbGVyZ3kgaW5mb3JtYXRpb248L2g1PlxuPHA+Tm8gYWxsZXJnZW5zLiBGcmVlIGZyb20gc3VscGhpdGVzLjwvcD5cbjx0YWJsZSB3aWR0aD1cIjEwMCVcIj5cbjx0Ym9keT5cbjx0cj5cbjx0ZD48c3Ryb25nPlR5cGljYWwgdmFsdWVzPC9zdHJvbmc+PC90ZD5cbjx0ZD48c3Ryb25nPlBlciAxMDBnPC9zdHJvbmc+PC90ZD5cbjwvdHI+XG48dHI+XG48dGQ+RW5lcmd5PC90ZD5cbjx0ZD4xMDcxa0ogKDI1MmtjYWwpPC90ZD5cbjwvdHI+XG48dHI+XG48dGQ+RmF0PC90ZD5cbjx0ZD4wLjBnPC90ZD5cbjwvdHI+XG48dHI+XG48dGQ+b2Ygd2hpY2ggc2F0dXJhdGVzPC90ZD5cbjx0ZD4wLjBnPC90ZD5cbjwvdHI+XG48dHI+XG48dGQ+Q2FyYm9oeWRyYXRlPC90ZD5cbjx0ZD42Mi45ZzwvdGQ+XG48L3RyPlxuPHRyPlxuPHRkPm9mIHdoaWNoIHN1Z2Fycyo8L3RkPlxuPHRkPjUyLjhnPC90ZD5cbjwvdHI+XG48dHI+XG48dGQ+UHJvdGVpbjwvdGQ+XG48dGQ+MC4xZzwvdGQ+XG48L3RyPlxuPHRyPlxuPHRkPlNhbHQ8L3RkPlxuPHRkPjAuMGc8L3RkPlxuPC90cj5cbjwvdGJvZHk+XG48L3RhYmxlPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5Nb3JlPC9oNT5cbjxwPlByb2R1Y3Qgb2bCoERvcnNldDxicj5TdWl0YWJsZSBmb3IgdmVnYW5zIGFuZCB2ZWdldGFyaWFuczxicj48L3A+XG48L2xpPiIsImRmYy1iOmhhc1F1YW50aXR5IjoiXzpiMTE5IiwiZGZjLWI6aW1hZ2UiOiJodHRwczovL2Nkbi5zaG9waWZ5LmNvbS9zL2ZpbGVzLzEvMDczMS84NDgzLzc5MzkvcHJvZHVjdHMvTGliZXJ0eS1GaWVsZHMtQXBwbGUtU3lydXAtMjAwbWxfYjFkMWU4Y2MtOTUzMC00MGZlLTk2ZDgtNWM1MmY2ZGE4YTAwLmpwZz92PTE2Nzc3NjA3NzQiLCJkZmMtYjpuYW1lIjoiQXBwbGUgU3lydXAgLSBDYXNlLCA2IHggMjUwbWwiLCJkZmMtYjpyZWZlcmVuY2VkQnkiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjY0MDIwOTkvQ2F0YWxvZ0l0ZW0ifSx7IkBpZCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjQwMjA5OS9DYXRhbG9nSXRlbSIsIkB0eXBlIjoiZGZjLWI6Q2F0YWxvZ0l0ZW0iLCJkZmMtYjpvZmZlcmVkVGhyb3VnaCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjQwMjA5OS9PZmZlciIsImRmYy1iOnNrdSI6IkxJQi9OQVNZUi9DNiIsImRmYy1iOnN0b2NrTGltaXRhdGlvbiI6Ii0xIn0seyJAaWQiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjY0MDIwOTkvT2ZmZXIiLCJAdHlwZSI6ImRmYy1iOk9mZmVyIiwiZGZjLWI6aGFzUHJpY2UiOnsiQGlkIjoiXzpiMTIwIn19XX0= + recorded_at: Fri, 02 Aug 2024 05:18:54 GMT recorded_with: VCR 6.2.0 diff --git a/spec/system/admin/dfc_product_import_spec.rb b/spec/system/admin/dfc_product_import_spec.rb index dff70ff168..64ca6ce30c 100644 --- a/spec/system/admin/dfc_product_import_spec.rb +++ b/spec/system/admin/dfc_product_import_spec.rb @@ -53,7 +53,7 @@ RSpec.describe "DFC Product Import" do select enterprise.name, from: "Enterprise" - url = "https://food-data-collaboration-produc-fe870152f634.herokuapp.com/fdc/products?shop=test-hodmedod.myshopify.com" + url = "https://env-0105831.jcloud-ver-jpe.ik-server.com/api/dfc/Enterprises/test-hodmedod/SuppliedProducts" fill_in "catalog_url", with: url expect { From ec828c335d08280588016de8ba589ffc7f96239f Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Fri, 2 Aug 2024 15:38:12 +1000 Subject: [PATCH 014/206] Remove superfluous FDC-specific request class --- .../admin/dfc_product_imports_controller.rb | 8 +---- .../dfc_provider/app/services/fdc_request.rb | 10 ------ .../spec/services/dfc_request_spec.rb | 24 +++++++++++++ .../spec/services/fdc_request_spec.rb | 35 ------------------- ...s_token_and_retrieves_the_FDC_catalog.yml} | 28 +++++++-------- 5 files changed, 39 insertions(+), 66 deletions(-) delete mode 100644 engines/dfc_provider/app/services/fdc_request.rb delete mode 100644 engines/dfc_provider/spec/services/fdc_request_spec.rb rename spec/fixtures/vcr_cassettes/{FdcRequest/refreshes_the_access_token_and_retrieves_a_catalog.yml => DfcRequest/refreshes_the_access_token_and_retrieves_the_FDC_catalog.yml} (93%) diff --git a/app/controllers/admin/dfc_product_imports_controller.rb b/app/controllers/admin/dfc_product_imports_controller.rb index c1076ea775..e85170db2f 100644 --- a/app/controllers/admin/dfc_product_imports_controller.rb +++ b/app/controllers/admin/dfc_product_imports_controller.rb @@ -35,13 +35,7 @@ module Admin private def fetch_catalog(url) - if url =~ /food-data-collaboration/ - fdc_json = FdcRequest.new(spree_current_user).call(url) - fdc_message = JSON.parse(fdc_json) - fdc_message["products"] - else - DfcRequest.new(spree_current_user).call(url) - end + DfcRequest.new(spree_current_user).call(url) end # Most of this code is the same as in the DfcProvider::SuppliedProductsController. diff --git a/engines/dfc_provider/app/services/fdc_request.rb b/engines/dfc_provider/app/services/fdc_request.rb deleted file mode 100644 index 6bb55926a8..0000000000 --- a/engines/dfc_provider/app/services/fdc_request.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -# Request a JSON document from the FDC API with authentication. -# -# This class was created when the FDC didn't comply with the DFC standard. -# But now it does and this class is empty. :-) -# -# We can delete this in the next commit. -class FdcRequest < DfcRequest -end diff --git a/engines/dfc_provider/spec/services/dfc_request_spec.rb b/engines/dfc_provider/spec/services/dfc_request_spec.rb index ea17ae02af..0de024b670 100644 --- a/engines/dfc_provider/spec/services/dfc_request_spec.rb +++ b/engines/dfc_provider/spec/services/dfc_request_spec.rb @@ -59,4 +59,28 @@ RSpec.describe DfcRequest do # would raise errors because we didn't setup Webmock or VCR. # The absence of errors makes this test pass. end + + it "refreshes the access token and retrieves the FDC catalog", vcr: true do + # A refresh is only attempted if the token is stale. + account.uid = "testdfc@protonmail.com" + account.refresh_token = ENV.fetch("OPENID_REFRESH_TOKEN") + account.updated_at = 1.day.ago + + response = nil + expect { + response = api.call( + "https://env-0105831.jcloud-ver-jpe.ik-server.com/api/dfc/Enterprises/test-hodmedod/SuppliedProducts" + ) + }.to change { + account.token + }.and change { + account.refresh_token + } + + json = JSON.parse(response) + + graph = DfcIo.import(json) + products = graph.select { |s| s.semanticType == "dfc-b:SuppliedProduct" } + expect(products).to be_present + end end diff --git a/engines/dfc_provider/spec/services/fdc_request_spec.rb b/engines/dfc_provider/spec/services/fdc_request_spec.rb deleted file mode 100644 index fa0c7990bc..0000000000 --- a/engines/dfc_provider/spec/services/fdc_request_spec.rb +++ /dev/null @@ -1,35 +0,0 @@ -# frozen_string_literal: true - -require_relative "../spec_helper" - -RSpec.describe FdcRequest do - subject(:api) { FdcRequest.new(user) } - - let(:user) { build(:oidc_user) } - let(:account) { user.oidc_account } - let(:url) { - "https://env-0105831.jcloud-ver-jpe.ik-server.com/api/dfc/Enterprises/test-hodmedod/SuppliedProducts" - } - - it "refreshes the access token and retrieves a catalog", vcr: true do - # A refresh is only attempted if the token is stale. - account.uid = "testdfc@protonmail.com" - account.refresh_token = ENV.fetch("OPENID_REFRESH_TOKEN") - account.updated_at = 1.day.ago - - response = nil - expect { - response = api.call(url) - }.to change { - account.token - }.and change { - account.refresh_token - } - - json = JSON.parse(response) - - graph = DfcIo.import(json) - products = graph.select { |s| s.semanticType == "dfc-b:SuppliedProduct" } - expect(products).to be_present - end -end diff --git a/spec/fixtures/vcr_cassettes/FdcRequest/refreshes_the_access_token_and_retrieves_a_catalog.yml b/spec/fixtures/vcr_cassettes/DfcRequest/refreshes_the_access_token_and_retrieves_the_FDC_catalog.yml similarity index 93% rename from spec/fixtures/vcr_cassettes/FdcRequest/refreshes_the_access_token_and_retrieves_a_catalog.yml rename to spec/fixtures/vcr_cassettes/DfcRequest/refreshes_the_access_token_and_retrieves_the_FDC_catalog.yml index c96f019513..f162e73216 100644 --- a/spec/fixtures/vcr_cassettes/FdcRequest/refreshes_the_access_token_and_retrieves_a_catalog.yml +++ b/spec/fixtures/vcr_cassettes/DfcRequest/refreshes_the_access_token_and_retrieves_the_FDC_catalog.yml @@ -25,7 +25,7 @@ http_interactions: Server: - openresty Date: - - Fri, 02 Aug 2024 05:18:48 GMT + - Fri, 02 Aug 2024 05:35:50 GMT Content-Type: - application/json; charset=utf-8 Content-Length: @@ -43,7 +43,7 @@ http_interactions: body: encoding: UTF-8 string: '{"message":"User access denied - token missing","error":"User not authorized"}' - recorded_at: Fri, 02 Aug 2024 05:18:49 GMT + recorded_at: Fri, 02 Aug 2024 05:35:50 GMT - request: method: get uri: https://login.lescommuns.org/auth/realms/data-food-consortium/.well-known/openid-configuration @@ -63,7 +63,7 @@ http_interactions: message: OK headers: Date: - - Fri, 02 Aug 2024 05:18:50 GMT + - Fri, 02 Aug 2024 05:35:52 GMT Content-Type: - application/json;charset=UTF-8 Transfer-Encoding: @@ -73,7 +73,7 @@ http_interactions: Vary: - Accept-Encoding Set-Cookie: - - AUTH_SESSION_ID=1722575931.041.145390.129545|78230f584c0d7db97d376e98de5321dc; + - AUTH_SESSION_ID=1722576953.323.145752.731488|78230f584c0d7db97d376e98de5321dc; Path=/; Secure; HttpOnly Cache-Control: - no-cache, must-revalidate, no-transform, no-store @@ -91,7 +91,7 @@ http_interactions: encoding: ASCII-8BIT string: '{"issuer":"https://login.lescommuns.org/auth/realms/data-food-consortium","authorization_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/auth","token_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/token","introspection_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/token/introspect","userinfo_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/userinfo","end_session_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/logout","frontchannel_logout_session_supported":true,"frontchannel_logout_supported":true,"jwks_uri":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/certs","check_session_iframe":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/login-status-iframe.html","grant_types_supported":["authorization_code","implicit","refresh_token","password","client_credentials","urn:openid:params:grant-type:ciba","urn:ietf:params:oauth:grant-type:device_code"],"acr_values_supported":["0","1"],"response_types_supported":["code","none","id_token","token","id_token token","code id_token","code token","code id_token token"],"subject_types_supported":["public","pairwise"],"id_token_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512"],"id_token_encryption_alg_values_supported":["RSA-OAEP","RSA-OAEP-256","RSA1_5"],"id_token_encryption_enc_values_supported":["A256GCM","A192GCM","A128GCM","A128CBC-HS256","A192CBC-HS384","A256CBC-HS512"],"userinfo_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512","none"],"userinfo_encryption_alg_values_supported":["RSA-OAEP","RSA-OAEP-256","RSA1_5"],"userinfo_encryption_enc_values_supported":["A256GCM","A192GCM","A128GCM","A128CBC-HS256","A192CBC-HS384","A256CBC-HS512"],"request_object_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512","none"],"request_object_encryption_alg_values_supported":["RSA-OAEP","RSA-OAEP-256","RSA1_5"],"request_object_encryption_enc_values_supported":["A256GCM","A192GCM","A128GCM","A128CBC-HS256","A192CBC-HS384","A256CBC-HS512"],"response_modes_supported":["query","fragment","form_post","query.jwt","fragment.jwt","form_post.jwt","jwt"],"registration_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/clients-registrations/openid-connect","token_endpoint_auth_methods_supported":["private_key_jwt","client_secret_basic","client_secret_post","tls_client_auth","client_secret_jwt"],"token_endpoint_auth_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512"],"introspection_endpoint_auth_methods_supported":["private_key_jwt","client_secret_basic","client_secret_post","tls_client_auth","client_secret_jwt"],"introspection_endpoint_auth_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512"],"authorization_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512"],"authorization_encryption_alg_values_supported":["RSA-OAEP","RSA-OAEP-256","RSA1_5"],"authorization_encryption_enc_values_supported":["A256GCM","A192GCM","A128GCM","A128CBC-HS256","A192CBC-HS384","A256CBC-HS512"],"claims_supported":["aud","sub","iss","auth_time","name","given_name","family_name","preferred_username","email","acr"],"claim_types_supported":["normal"],"claims_parameter_supported":true,"scopes_supported":["openid","microprofile-jwt","phone","roles","profile","email","address","web-origins","acr","offline_access"],"request_parameter_supported":true,"request_uri_parameter_supported":true,"require_request_uri_registration":true,"code_challenge_methods_supported":["plain","S256"],"tls_client_certificate_bound_access_tokens":true,"revocation_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/revoke","revocation_endpoint_auth_methods_supported":["private_key_jwt","client_secret_basic","client_secret_post","tls_client_auth","client_secret_jwt"],"revocation_endpoint_auth_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512"],"backchannel_logout_supported":true,"backchannel_logout_session_supported":true,"device_authorization_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/auth/device","backchannel_token_delivery_modes_supported":["poll","ping"],"backchannel_authentication_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/ext/ciba/auth","backchannel_authentication_request_signing_alg_values_supported":["PS384","ES384","RS384","ES256","RS256","ES512","PS256","PS512","RS512"],"require_pushed_authorization_requests":false,"pushed_authorization_request_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/ext/par/request","mtls_endpoint_aliases":{"token_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/token","revocation_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/revoke","introspection_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/token/introspect","device_authorization_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/auth/device","registration_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/clients-registrations/openid-connect","userinfo_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/userinfo","pushed_authorization_request_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/ext/par/request","backchannel_authentication_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/ext/ciba/auth"},"authorization_response_iss_parameter_supported":true}' - recorded_at: Fri, 02 Aug 2024 05:18:50 GMT + recorded_at: Fri, 02 Aug 2024 05:35:52 GMT - request: method: post uri: https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/token @@ -115,7 +115,7 @@ http_interactions: message: OK headers: Date: - - Fri, 02 Aug 2024 05:18:51 GMT + - Fri, 02 Aug 2024 05:35:53 GMT Content-Type: - application/json Transfer-Encoding: @@ -125,7 +125,7 @@ http_interactions: Vary: - Accept-Encoding Set-Cookie: - - AUTH_SESSION_ID=1722575932.27.145456.739538|78230f584c0d7db97d376e98de5321dc; + - AUTH_SESSION_ID=1722576954.541.145720.245289|78230f584c0d7db97d376e98de5321dc; Path=/; Secure; HttpOnly Cache-Control: - no-store @@ -143,9 +143,9 @@ http_interactions: - 1; mode=block body: encoding: ASCII-8BIT - string: '{"access_token":"","expires_in":1800,"refresh_expires_in":31454045,"refresh_token":"","token_type":"Bearer","id_token":"","not-before-policy":0,"session_state":"207aea32-9912-47cb-b8ad-7508448912b8","scope":"openid + string: '{"access_token":"","expires_in":1800,"refresh_expires_in":31453023,"refresh_token":"","token_type":"Bearer","id_token":"","not-before-policy":0,"session_state":"207aea32-9912-47cb-b8ad-7508448912b8","scope":"openid profile email"}' - recorded_at: Fri, 02 Aug 2024 05:18:51 GMT + recorded_at: Fri, 02 Aug 2024 05:35:53 GMT - request: method: get uri: https://env-0105831.jcloud-ver-jpe.ik-server.com/api/dfc/Enterprises/test-hodmedod/SuppliedProducts @@ -171,7 +171,7 @@ http_interactions: Server: - openresty Date: - - Fri, 02 Aug 2024 05:18:53 GMT + - Fri, 02 Aug 2024 05:35:55 GMT Content-Type: - text/html; charset=utf-8 Content-Length: @@ -183,16 +183,16 @@ http_interactions: Access-Control-Allow-Origin: - "*" Etag: - - W/"3be1-LGnj4P77pi1685bLhkZes+kuO7c" + - W/"3be1-Kh0ReRKdXCT7KNerKk4EkoR/TQI" Set-Cookie: - SRVGROUP=common; path=/; HttpOnly X-Resolver-Ip: - - 185.172.100.60 + - 185.172.100.59 Strict-Transport-Security: - max-age=15811200 body: encoding: ASCII-8BIT string: !binary |- - eyJAY29udGV4dCI6Imh0dHBzOi8vd3d3LmRhdGFmb29kY29uc29ydGl1bS5vcmciLCJAZ3JhcGgiOlt7IkBpZCI6Il86YjExMyIsIkB0eXBlIjoiZGZjLWI6UXVhbnRpdGF0aXZlVmFsdWUiLCJkZmMtYjpoYXNVbml0IjoiZGZjLW06S2lsb2dyYW0iLCJkZmMtYjp2YWx1ZSI6IjAuMDQifSx7IkBpZCI6Il86YjExNCIsIkB0eXBlIjoiZGZjLWI6UHJpY2UiLCJkZmMtYjpWQVRyYXRlIjoiMCIsImRmYy1iOmhhc1VuaXQiOiJkZmMtbTpFdXJvIiwiZGZjLWI6dmFsdWUiOiI2LjI1In0seyJAaWQiOiJfOmIxMTUiLCJAdHlwZSI6ImRmYy1iOlF1YW50aXRhdGl2ZVZhbHVlIiwiZGZjLWI6aGFzVW5pdCI6ImRmYy1tOktpbG9ncmFtIiwiZGZjLWI6dmFsdWUiOiIwLjI1In0seyJAaWQiOiJfOmIxMTYiLCJAdHlwZSI6ImRmYy1iOlByaWNlIiwiZGZjLWI6VkFUcmF0ZSI6IjAiLCJkZmMtYjpoYXNVbml0IjoiZGZjLW06RXVybyIsImRmYy1iOnZhbHVlIjoiNy4wMCJ9LHsiQGlkIjoiXzpiMTE3IiwiQHR5cGUiOiJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZSIsImRmYy1iOmhhc1VuaXQiOiJkZmMtbTpLaWxvZ3JhbSIsImRmYy1iOnZhbHVlIjoiMC4yNCJ9LHsiQGlkIjoiXzpiMTE4IiwiQHR5cGUiOiJkZmMtYjpQcmljZSIsImRmYy1iOlZBVHJhdGUiOiIwIiwiZGZjLWI6aGFzVW5pdCI6ImRmYy1tOkV1cm8iLCJkZmMtYjp2YWx1ZSI6IjMwLjIwIn0seyJAaWQiOiJfOmIxMTkiLCJAdHlwZSI6ImRmYy1iOlF1YW50aXRhdGl2ZVZhbHVlIiwiZGZjLWI6aGFzVW5pdCI6ImRmYy1tOktpbG9ncmFtIiwiZGZjLWI6dmFsdWUiOiIxLjUifSx7IkBpZCI6Il86YjEyMCIsIkB0eXBlIjoiZGZjLWI6UHJpY2UiLCJkZmMtYjpWQVRyYXRlIjoiMCIsImRmYy1iOmhhc1VuaXQiOiJkZmMtbTpFdXJvIiwiZGZjLWI6dmFsdWUiOiI3OS4wMCJ9LHsiQGlkIjoiXzpiMTIxIiwiQHR5cGUiOiJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZSIsImRmYy1iOmhhc1VuaXQiOiJkZmMtbTpQaWVjZSIsImRmYy1iOnZhbHVlIjoiNiJ9LHsiQGlkIjoiXzpiMTIyIiwiQHR5cGUiOiJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZSIsImRmYy1iOmhhc1VuaXQiOiJkZmMtbTpQaWVjZSIsImRmYy1iOnZhbHVlIjoiMSJ9LHsiQGlkIjoiXzpiMTIzIiwiQHR5cGUiOiJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZSIsImRmYy1iOmhhc1VuaXQiOiJkZmMtbTpQaWVjZSIsImRmYy1iOnZhbHVlIjoiNiJ9LHsiQGlkIjoiXzpiMTI0IiwiQHR5cGUiOiJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZSIsImRmYy1iOmhhc1VuaXQiOiJkZmMtbTpQaWVjZSIsImRmYy1iOnZhbHVlIjoiMSJ9LHsiQGlkIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2MjM4MjU5IiwiQHR5cGUiOiJkZmMtYjpTdXBwbGllZFByb2R1Y3QiLCJkZmMtYjpkZXNjcmlwdGlvbiI6Ijx0YWJsZSB3aWR0aD1cIjEwMCVcIj5cbjx0Ym9keT5cbjx0ciBzdHlsZT1cImJvcmRlcjogMHB4O1wiIGRhdGEtbWNlLXN0eWxlPVwiYm9yZGVyOiAwcHg7XCI+XG48dGQgYmdjb2xvcj1cIiNkNmZiZWRcIiBzdHlsZT1cImNvbG9yOiAjMDAwMDAwOyBib3JkZXI6IDBweDsgd2lkdGg6IDUyNnB4O1wiIGRhdGEtbWNlLXN0eWxlPVwiY29sb3I6ICMwMDAwMDA7IGJvcmRlcjogMHB4OyB3aWR0aDogNTI2cHg7XCI+PGI+VGhpcyByaWNoLCBpbnRlbnNlIGFuZCBkZWVwbHkgZmxhdm91cmVkIDYteWVhciBvbGQgYXBwbGUgYmFsc2FtaWMgdmluZWdhciBpcyBtYWRlIHVzaW5nIHRoZSB0cmFkaXRpb25hbCBJdGFsaWFuIG1ldGhvZCBvZiByZWR1Y3Rpb24gYW5kIGNvbmNlbnRyYXRpb24gb2YgdGhlIGp1aWNlIG92ZXIgYSBsZW5ndGh5IHBlcmlvZCBvZiB0aW1lLCByYXRoZXIgdGhhbiBieSBhZGRpbmcgZmxhdm91cmluZyBhbmQgY29sb3VyaW5nLsKgPC9iPjwvdGQ+XG48L3RyPlxuPC90Ym9keT5cbjwvdGFibGU+XG48cD5MaWJlcnR5IEZpZWxkcyBwcm9kdWNlIHNtYWxsIGJhdGNoZXMgb2Ygc3VwZXJiIHN5cnVwLCBiYWxzYW1pYyB2aW5lZ2FyLCBjaWRlciBhbmQgdm9ka2EgYnkgaGFuZCBmcm9tIHRoZSBmcnVpdCBvZiB0aGVpciBvd24gRG9yc2V0IGFwcGxlIG9yY2hhcmRzLCBwbGFudGVkIGZyb20gMjAxMC48YnI+PC9wPlxuPHA+VGhlIGJhbHNhbWljIHZpbmVnYXIgaXMgYWdlZCBmb3IgNiB5ZWFycyBpbiBiYXJyZWxzLsKgVGhlIG9ubHkgaW5ncmVkaWVudCBpcyBhcHBsZXMuPC9wPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5Ib3cgdG8gdXNlPC9oNT5cbjxwPlVzZSBsaWtlIEl0YWxpYW4gYmFsc2FtaWMgdmluZWdhci7CoDxzcGFuIGRhdGEtbWNlLWZyYWdtZW50PVwiMVwiPkFzIHdlbGwgYXMgdXNpbmcgb24gc2FsYWRzLCBpdOKAmXMgYSBncmVhdCBwYXJ0bmVyIGZvciBncmlsbGVkIG1lYXRzIG9yIGNoYXJjdXRlcmllOyBhIGRyb3AgYnJpbmdzIG91dCB0aGUgdGFzdGUgb2Ygc3RyYXdiZXJyaWVzIGFuZCBvdGhlciBzb2Z0IGZydWl0czsgYW5kIGl0IGNhbiByZWFsbHkgZW5oYW5jZSBhIHN0ZXcsIHNhdWNlIG9yIGEgc291cC7CoDwvc3Bhbj48L3A+XG48aDUgY2xhc3M9XCJwcm9kdWN0LWRldGFpbC10aXRsZVwiPlRvIHN0b3JlPGJyPlxuPC9oNT5cbjxwPkZvciBiZXN0IGJlZm9yZSBkYXRlIHNlZSBwYWNrLiBTdG9yZSBpbiBhIGNvb2wsIGRyeSBwbGFjZS48YnI+PC9wPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5JbmdyZWRpZW50czwvaDU+XG48cD5BcHBsZXM8YnI+PC9wPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5BbGxlcmd5IGluZm9ybWF0aW9uPC9oNT5cbjxwPk5vIGFsbGVyZ2Vucy48L3A+PGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5Nb3JlPC9oNT5cbjxwPlByb2R1Y3Qgb2bCoERvcnNldDxicj5TdWl0YWJsZSBmb3IgdmVnYW5zIGFuZCB2ZWdldGFyaWFuczxicj48L3A+IiwiZGZjLWI6aGFzUXVhbnRpdHkiOiJfOmIxMTMiLCJkZmMtYjppbWFnZSI6Imh0dHBzOi8vY2RuLnNob3BpZnkuY29tL3MvZmlsZXMvMS8wNzMxLzg0ODMvNzkzOS9wcm9kdWN0cy9MaWJlcnR5LUZpZWxkcy1BcHBsZS1CYWxzYW1pYy1WaW5lZ2FyLTQwbWxfNzk2MTdlZWEtYWI4Yy00MDcwLTllNGQtNzExYmYwMzBhZDA3LmpwZz92PTE2Nzc3NjA3NzIiLCJkZmMtYjpuYW1lIjoiQXBwbGUgQmFsc2FtaWMgVmluZWdhciAtIFJldGFpbCBib3R0bGUsIDQwbWwiLCJkZmMtYjpyZWZlcmVuY2VkQnkiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjYyMzgyNTkvQ2F0YWxvZ0l0ZW0ifSx7IkBpZCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjIzODI1OS9Bc1BsYW5uZWRDb25zdW1wdGlvbkZsb3ciLCJAdHlwZSI6ImRmYy1iOkFzUGxhbm5lZENvbnN1bXB0aW9uRmxvdyIsImRmYy1iOmNvbnN1bWVzIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2MjM4MjU5IiwiZGZjLWI6aGFzUXVhbnRpdHkiOiJfOmIxMjEifSx7IkBpZCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjIzODI1OS9Bc1BsYW5uZWRQcm9kdWN0aW9uRmxvdyIsIkB0eXBlIjoiZGZjLWI6QXNQbGFubmVkUHJvZHVjdGlvbkZsb3ciLCJkZmMtYjpoYXNRdWFudGl0eSI6Il86YjEyMiIsImRmYy1iOnByb2R1Y2VzIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2MjcxMDI3In0seyJAaWQiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjYyMzgyNTkvQXNQbGFubmVkVHJhbnNmb3JtYXRpb24iLCJAdHlwZSI6ImRmYy1iOkFzUGxhbm5lZFRyYW5zZm9ybWF0aW9uIiwiZGZjLWI6aGFzSW5jb21lIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2MjM4MjU5L0FzUGxhbm5lZENvbnN1bXB0aW9uRmxvdyIsImRmYy1iOmhhc091dGNvbWUiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjYyMzgyNTkvQXNQbGFubmVkUHJvZHVjdGlvbkZsb3cifSx7IkBpZCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjIzODI1OS9DYXRhbG9nSXRlbSIsIkB0eXBlIjoiZGZjLWI6Q2F0YWxvZ0l0ZW0iLCJkZmMtYjpvZmZlcmVkVGhyb3VnaCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjIzODI1OS9PZmZlciIsImRmYy1iOnNrdSI6IkxJQi9OQUJWSS9CRiIsImRmYy1iOnN0b2NrTGltaXRhdGlvbiI6Ii0xIn0seyJAaWQiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjYyMzgyNTkvT2ZmZXIiLCJAdHlwZSI6ImRmYy1iOk9mZmVyIiwiZGZjLWI6aGFzUHJpY2UiOnsiQGlkIjoiXzpiMTE0In19LHsiQGlkIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2MjcxMDI3IiwiQHR5cGUiOiJkZmMtYjpTdXBwbGllZFByb2R1Y3QiLCJkZmMtYjpkZXNjcmlwdGlvbiI6Ijx0YWJsZSB3aWR0aD1cIjEwMCVcIj5cbjx0Ym9keT5cbjx0ciBzdHlsZT1cImJvcmRlcjogMHB4O1wiIGRhdGEtbWNlLXN0eWxlPVwiYm9yZGVyOiAwcHg7XCI+XG48dGQgYmdjb2xvcj1cIiNkNmZiZWRcIiBzdHlsZT1cImNvbG9yOiAjMDAwMDAwOyBib3JkZXI6IDBweDsgd2lkdGg6IDUyNnB4O1wiIGRhdGEtbWNlLXN0eWxlPVwiY29sb3I6ICMwMDAwMDA7IGJvcmRlcjogMHB4OyB3aWR0aDogNTI2cHg7XCI+PGI+VGhpcyByaWNoLCBpbnRlbnNlIGFuZCBkZWVwbHkgZmxhdm91cmVkIDYteWVhciBvbGQgYXBwbGUgYmFsc2FtaWMgdmluZWdhciBpcyBtYWRlIHVzaW5nIHRoZSB0cmFkaXRpb25hbCBJdGFsaWFuIG1ldGhvZCBvZiByZWR1Y3Rpb24gYW5kIGNvbmNlbnRyYXRpb24gb2YgdGhlIGp1aWNlIG92ZXIgYSBsZW5ndGh5IHBlcmlvZCBvZiB0aW1lLCByYXRoZXIgdGhhbiBieSBhZGRpbmcgZmxhdm91cmluZyBhbmQgY29sb3VyaW5nLsKgPC9iPjwvdGQ+XG48L3RyPlxuPC90Ym9keT5cbjwvdGFibGU+XG48cD5MaWJlcnR5IEZpZWxkcyBwcm9kdWNlIHNtYWxsIGJhdGNoZXMgb2Ygc3VwZXJiIHN5cnVwLCBiYWxzYW1pYyB2aW5lZ2FyLCBjaWRlciBhbmQgdm9ka2EgYnkgaGFuZCBmcm9tIHRoZSBmcnVpdCBvZiB0aGVpciBvd24gRG9yc2V0IGFwcGxlIG9yY2hhcmRzLCBwbGFudGVkIGZyb20gMjAxMC48YnI+PC9wPlxuPHA+VGhlIGJhbHNhbWljIHZpbmVnYXIgaXMgYWdlZCBmb3IgNiB5ZWFycyBpbiBiYXJyZWxzLsKgVGhlIG9ubHkgaW5ncmVkaWVudCBpcyBhcHBsZXMuPC9wPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5Ib3cgdG8gdXNlPC9oNT5cbjxwPlVzZSBsaWtlIEl0YWxpYW4gYmFsc2FtaWMgdmluZWdhci7CoDxzcGFuIGRhdGEtbWNlLWZyYWdtZW50PVwiMVwiPkFzIHdlbGwgYXMgdXNpbmcgb24gc2FsYWRzLCBpdOKAmXMgYSBncmVhdCBwYXJ0bmVyIGZvciBncmlsbGVkIG1lYXRzIG9yIGNoYXJjdXRlcmllOyBhIGRyb3AgYnJpbmdzIG91dCB0aGUgdGFzdGUgb2Ygc3RyYXdiZXJyaWVzIGFuZCBvdGhlciBzb2Z0IGZydWl0czsgYW5kIGl0IGNhbiByZWFsbHkgZW5oYW5jZSBhIHN0ZXcsIHNhdWNlIG9yIGEgc291cC7CoDwvc3Bhbj48L3A+XG48aDUgY2xhc3M9XCJwcm9kdWN0LWRldGFpbC10aXRsZVwiPlRvIHN0b3JlPGJyPlxuPC9oNT5cbjxwPkZvciBiZXN0IGJlZm9yZSBkYXRlIHNlZSBwYWNrLiBTdG9yZSBpbiBhIGNvb2wsIGRyeSBwbGFjZS48YnI+PC9wPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5JbmdyZWRpZW50czwvaDU+XG48cD5BcHBsZXM8YnI+PC9wPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5BbGxlcmd5IGluZm9ybWF0aW9uPC9oNT5cbjxwPk5vIGFsbGVyZ2Vucy48L3A+PGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5Nb3JlPC9oNT5cbjxwPlByb2R1Y3Qgb2bCoERvcnNldDxicj5TdWl0YWJsZSBmb3IgdmVnYW5zIGFuZCB2ZWdldGFyaWFuczxicj48L3A+IiwiZGZjLWI6aGFzUXVhbnRpdHkiOiJfOmIxMTciLCJkZmMtYjppbWFnZSI6Imh0dHBzOi8vY2RuLnNob3BpZnkuY29tL3MvZmlsZXMvMS8wNzMxLzg0ODMvNzkzOS9wcm9kdWN0cy9MaWJlcnR5LUZpZWxkcy1BcHBsZS1CYWxzYW1pYy1WaW5lZ2FyLTQwbWxfNzk2MTdlZWEtYWI4Yy00MDcwLTllNGQtNzExYmYwMzBhZDA3LmpwZz92PTE2Nzc3NjA3NzIiLCJkZmMtYjpuYW1lIjoiQXBwbGUgQmFsc2FtaWMgVmluZWdhciAtIENhc2UsIDYgeCA0MG1sIiwiZGZjLWI6cmVmZXJlbmNlZEJ5IjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2MjcxMDI3L0NhdGFsb2dJdGVtIn0seyJAaWQiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjYyNzEwMjcvQ2F0YWxvZ0l0ZW0iLCJAdHlwZSI6ImRmYy1iOkNhdGFsb2dJdGVtIiwiZGZjLWI6b2ZmZXJlZFRocm91Z2giOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjYyNzEwMjcvT2ZmZXIiLCJkZmMtYjpza3UiOiJMSUIvTkFCVkkvQzYiLCJkZmMtYjpzdG9ja0xpbWl0YXRpb24iOiI1NSJ9LHsiQGlkIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2MjcxMDI3L09mZmVyIiwiQHR5cGUiOiJkZmMtYjpPZmZlciIsImRmYy1iOmhhc1ByaWNlIjp7IkBpZCI6Il86YjExOCJ9fSx7IkBpZCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjM2OTMzMSIsIkB0eXBlIjoiZGZjLWI6U3VwcGxpZWRQcm9kdWN0IiwiZGZjLWI6ZGVzY3JpcHRpb24iOiI8dGFibGUgd2lkdGg9XCIxMDAlXCI+XG48dGJvZHk+XG48dHIgc3R5bGU9XCJib3JkZXI6IDBweDtcIiBkYXRhLW1jZS1zdHlsZT1cImJvcmRlcjogMHB4O1wiPlxuPHRkIHN0eWxlPVwiY29sb3I6ICMwMDAwMDA7IGJvcmRlcjogMHB4OyB3aWR0aDogNTI2cHg7XCIgYmdjb2xvcj1cIiNkNmZiZWRcIiBkYXRhLW1jZS1zdHlsZT1cImNvbG9yOiAjMDAwMDAwOyBib3JkZXI6IDBweDsgd2lkdGg6IDUyNnB4O1wiPjxiPkRvcnNldCdzIGFuc3dlciB0byBtYXBsZSBzeXJ1cCwgTGliZXJ0eSBGaWVsZHMnIEFwcGxlIFN5cnVwIGlzIGEgbHV4dXJpb3VzbHkgcmljaCBhbmQgaW50ZW5zZSBuYXR1cmFsIHN3ZWV0ZW5lciwgbWFkZSBvbmx5IGZyb20gdGhlIGFwcGxlcyBvZiB0aGVpciBvcmNoYXJkcy48L2I+PC90ZD5cbjwvdHI+XG48L3Rib2R5PlxuPC90YWJsZT5cbjxwPkxpYmVydHkgRmllbGRzIHByb2R1Y2Ugc21hbGwgYmF0Y2hlcyBvZiBzdXBlcmIgc3lydXAsIGJhbHNhbWljIHZpbmVnYXIsIGNpZGVyIGFuZCB2b2RrYSBieSBoYW5kIGZyb20gdGhlIGZydWl0IG9mIHRoZWlyIG93biBEb3JzZXQgYXBwbGUgb3JjaGFyZHMsIHBsYW50ZWQgZnJvbSAyMDEwLjxicj48L3A+XG48cD5UaGUgc3lydXAgY29udGFpbnMgbm8gYWRkaXRpdmVzLCBjaGVtaWNhbHMsIGZpbmluZyBhZ2VudHMgb3Igc3VnYXIuIE92ZXIgMmtnIGFwcGxlcyBnbyBpbnRvIGVhY2ggYm90dGxlLjwvcD5cbjxsaSBpZD1cInRhYjFcIiBjbGFzcz1cImFjdGl2ZVwiPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5Ib3cgdG8gdXNlPC9oNT5cbjxwPlVzZSBhcyBhIHN3ZWV0ZW5lciwgbGlrZSBtYXBsZSBzeXJ1cC7CoERlbGljaW91cyB3aXRoIHBhbmNha2VzLCB5b2dodXJ0IHBvcnJpZGdlLCBpY2UgY3JlYW0sIEZyZW5jaCB0b2FzdCBhbmQgbW9yZS4gTWl4IHdpdGggc3BhcmtsaW5nIHdhdGVyIG9yIGxlbW9uYWRlIHRvIG1ha2UgYSBub24tYWxjb2hvbGljIHN1bW1lciBjdXAsIG9yIHVzZSBhcyBhIHN3ZWV0ZW5lciBpbiBjb29raW5nLjwvcD5cbjxoNSBjbGFzcz1cInByb2R1Y3QtZGV0YWlsLXRpdGxlXCI+VG8gc3RvcmU8YnI+XG48L2g1PlxuPHA+Rm9yIGJlc3QgYmVmb3JlIGRhdGUgc2VlIHBhY2suIFN0b3JlIGluIGEgY29vbCwgZHJ5IHBsYWNlLiBSZWZyaWdlcmF0ZSBhZnRlciBvcGVuaW5nIGFuZCB1c2Ugd2l0aGluIDYgd2Vla3M8YnI+PC9wPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5JbmdyZWRpZW50czwvaDU+XG48cD5BcHBsZXM8YnI+PC9wPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5BbGxlcmd5IGluZm9ybWF0aW9uPC9oNT5cbjxwPk5vIGFsbGVyZ2Vucy4gRnJlZSBmcm9tIHN1bHBoaXRlcy48L3A+XG48dGFibGUgd2lkdGg9XCIxMDAlXCI+XG48dGJvZHk+XG48dHI+XG48dGQ+PHN0cm9uZz5UeXBpY2FsIHZhbHVlczwvc3Ryb25nPjwvdGQ+XG48dGQ+PHN0cm9uZz5QZXIgMTAwZzwvc3Ryb25nPjwvdGQ+XG48L3RyPlxuPHRyPlxuPHRkPkVuZXJneTwvdGQ+XG48dGQ+MTA3MWtKICgyNTJrY2FsKTwvdGQ+XG48L3RyPlxuPHRyPlxuPHRkPkZhdDwvdGQ+XG48dGQ+MC4wZzwvdGQ+XG48L3RyPlxuPHRyPlxuPHRkPm9mIHdoaWNoIHNhdHVyYXRlczwvdGQ+XG48dGQ+MC4wZzwvdGQ+XG48L3RyPlxuPHRyPlxuPHRkPkNhcmJvaHlkcmF0ZTwvdGQ+XG48dGQ+NjIuOWc8L3RkPlxuPC90cj5cbjx0cj5cbjx0ZD5vZiB3aGljaCBzdWdhcnMqPC90ZD5cbjx0ZD41Mi44ZzwvdGQ+XG48L3RyPlxuPHRyPlxuPHRkPlByb3RlaW48L3RkPlxuPHRkPjAuMWc8L3RkPlxuPC90cj5cbjx0cj5cbjx0ZD5TYWx0PC90ZD5cbjx0ZD4wLjBnPC90ZD5cbjwvdHI+XG48L3Rib2R5PlxuPC90YWJsZT5cbjxoNSBjbGFzcz1cInByb2R1Y3QtZGV0YWlsLXRpdGxlXCI+TW9yZTwvaDU+XG48cD5Qcm9kdWN0IG9mwqBEb3JzZXQ8YnI+U3VpdGFibGUgZm9yIHZlZ2FucyBhbmQgdmVnZXRhcmlhbnM8YnI+PC9wPlxuPC9saT4iLCJkZmMtYjpoYXNRdWFudGl0eSI6Il86YjExNSIsImRmYy1iOmltYWdlIjoiaHR0cHM6Ly9jZG4uc2hvcGlmeS5jb20vcy9maWxlcy8xLzA3MzEvODQ4My83OTM5L3Byb2R1Y3RzL0xpYmVydHktRmllbGRzLUFwcGxlLVN5cnVwLTIwMG1sX2IxZDFlOGNjLTk1MzAtNDBmZS05NmQ4LTVjNTJmNmRhOGEwMC5qcGc/dj0xNjc3NzYwNzc0IiwiZGZjLWI6bmFtZSI6IkFwcGxlIFN5cnVwIC0gUmV0YWlsIGJvdHRsZSwgMjUwbWwiLCJkZmMtYjpyZWZlcmVuY2VkQnkiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjYzNjkzMzEvQ2F0YWxvZ0l0ZW0ifSx7IkBpZCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjM2OTMzMS9Bc1BsYW5uZWRDb25zdW1wdGlvbkZsb3ciLCJAdHlwZSI6ImRmYy1iOkFzUGxhbm5lZENvbnN1bXB0aW9uRmxvdyIsImRmYy1iOmNvbnN1bWVzIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2MzY5MzMxIiwiZGZjLWI6aGFzUXVhbnRpdHkiOiJfOmIxMjMifSx7IkBpZCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjM2OTMzMS9Bc1BsYW5uZWRQcm9kdWN0aW9uRmxvdyIsIkB0eXBlIjoiZGZjLWI6QXNQbGFubmVkUHJvZHVjdGlvbkZsb3ciLCJkZmMtYjpoYXNRdWFudGl0eSI6Il86YjEyNCIsImRmYy1iOnByb2R1Y2VzIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2NDAyMDk5In0seyJAaWQiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjYzNjkzMzEvQXNQbGFubmVkVHJhbnNmb3JtYXRpb24iLCJAdHlwZSI6ImRmYy1iOkFzUGxhbm5lZFRyYW5zZm9ybWF0aW9uIiwiZGZjLWI6aGFzSW5jb21lIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2MzY5MzMxL0FzUGxhbm5lZENvbnN1bXB0aW9uRmxvdyIsImRmYy1iOmhhc091dGNvbWUiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjYzNjkzMzEvQXNQbGFubmVkUHJvZHVjdGlvbkZsb3cifSx7IkBpZCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjM2OTMzMS9DYXRhbG9nSXRlbSIsIkB0eXBlIjoiZGZjLWI6Q2F0YWxvZ0l0ZW0iLCJkZmMtYjpvZmZlcmVkVGhyb3VnaCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjM2OTMzMS9PZmZlciIsImRmYy1iOnNrdSI6IkxJQi9OQVNZUi9CVCIsImRmYy1iOnN0b2NrTGltaXRhdGlvbiI6Ii0xIn0seyJAaWQiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjYzNjkzMzEvT2ZmZXIiLCJAdHlwZSI6ImRmYy1iOk9mZmVyIiwiZGZjLWI6aGFzUHJpY2UiOnsiQGlkIjoiXzpiMTE2In19LHsiQGlkIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2NDAyMDk5IiwiQHR5cGUiOiJkZmMtYjpTdXBwbGllZFByb2R1Y3QiLCJkZmMtYjpkZXNjcmlwdGlvbiI6Ijx0YWJsZSB3aWR0aD1cIjEwMCVcIj5cbjx0Ym9keT5cbjx0ciBzdHlsZT1cImJvcmRlcjogMHB4O1wiIGRhdGEtbWNlLXN0eWxlPVwiYm9yZGVyOiAwcHg7XCI+XG48dGQgc3R5bGU9XCJjb2xvcjogIzAwMDAwMDsgYm9yZGVyOiAwcHg7IHdpZHRoOiA1MjZweDtcIiBiZ2NvbG9yPVwiI2Q2ZmJlZFwiIGRhdGEtbWNlLXN0eWxlPVwiY29sb3I6ICMwMDAwMDA7IGJvcmRlcjogMHB4OyB3aWR0aDogNTI2cHg7XCI+PGI+RG9yc2V0J3MgYW5zd2VyIHRvIG1hcGxlIHN5cnVwLCBMaWJlcnR5IEZpZWxkcycgQXBwbGUgU3lydXAgaXMgYSBsdXh1cmlvdXNseSByaWNoIGFuZCBpbnRlbnNlIG5hdHVyYWwgc3dlZXRlbmVyLCBtYWRlIG9ubHkgZnJvbSB0aGUgYXBwbGVzIG9mIHRoZWlyIG9yY2hhcmRzLjwvYj48L3RkPlxuPC90cj5cbjwvdGJvZHk+XG48L3RhYmxlPlxuPHA+TGliZXJ0eSBGaWVsZHMgcHJvZHVjZSBzbWFsbCBiYXRjaGVzIG9mIHN1cGVyYiBzeXJ1cCwgYmFsc2FtaWMgdmluZWdhciwgY2lkZXIgYW5kIHZvZGthIGJ5IGhhbmQgZnJvbSB0aGUgZnJ1aXQgb2YgdGhlaXIgb3duIERvcnNldCBhcHBsZSBvcmNoYXJkcywgcGxhbnRlZCBmcm9tIDIwMTAuPGJyPjwvcD5cbjxwPlRoZSBzeXJ1cCBjb250YWlucyBubyBhZGRpdGl2ZXMsIGNoZW1pY2FscywgZmluaW5nIGFnZW50cyBvciBzdWdhci4gT3ZlciAya2cgYXBwbGVzIGdvIGludG8gZWFjaCBib3R0bGUuPC9wPlxuPGxpIGlkPVwidGFiMVwiIGNsYXNzPVwiYWN0aXZlXCI+XG48aDUgY2xhc3M9XCJwcm9kdWN0LWRldGFpbC10aXRsZVwiPkhvdyB0byB1c2U8L2g1PlxuPHA+VXNlIGFzIGEgc3dlZXRlbmVyLCBsaWtlIG1hcGxlIHN5cnVwLsKgRGVsaWNpb3VzIHdpdGggcGFuY2FrZXMsIHlvZ2h1cnQgcG9ycmlkZ2UsIGljZSBjcmVhbSwgRnJlbmNoIHRvYXN0IGFuZCBtb3JlLiBNaXggd2l0aCBzcGFya2xpbmcgd2F0ZXIgb3IgbGVtb25hZGUgdG8gbWFrZSBhIG5vbi1hbGNvaG9saWMgc3VtbWVyIGN1cCwgb3IgdXNlIGFzIGEgc3dlZXRlbmVyIGluIGNvb2tpbmcuPC9wPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5UbyBzdG9yZTxicj5cbjwvaDU+XG48cD5Gb3IgYmVzdCBiZWZvcmUgZGF0ZSBzZWUgcGFjay4gU3RvcmUgaW4gYSBjb29sLCBkcnkgcGxhY2UuIFJlZnJpZ2VyYXRlIGFmdGVyIG9wZW5pbmcgYW5kIHVzZSB3aXRoaW4gNiB3ZWVrczxicj48L3A+XG48aDUgY2xhc3M9XCJwcm9kdWN0LWRldGFpbC10aXRsZVwiPkluZ3JlZGllbnRzPC9oNT5cbjxwPkFwcGxlczxicj48L3A+XG48aDUgY2xhc3M9XCJwcm9kdWN0LWRldGFpbC10aXRsZVwiPkFsbGVyZ3kgaW5mb3JtYXRpb248L2g1PlxuPHA+Tm8gYWxsZXJnZW5zLiBGcmVlIGZyb20gc3VscGhpdGVzLjwvcD5cbjx0YWJsZSB3aWR0aD1cIjEwMCVcIj5cbjx0Ym9keT5cbjx0cj5cbjx0ZD48c3Ryb25nPlR5cGljYWwgdmFsdWVzPC9zdHJvbmc+PC90ZD5cbjx0ZD48c3Ryb25nPlBlciAxMDBnPC9zdHJvbmc+PC90ZD5cbjwvdHI+XG48dHI+XG48dGQ+RW5lcmd5PC90ZD5cbjx0ZD4xMDcxa0ogKDI1MmtjYWwpPC90ZD5cbjwvdHI+XG48dHI+XG48dGQ+RmF0PC90ZD5cbjx0ZD4wLjBnPC90ZD5cbjwvdHI+XG48dHI+XG48dGQ+b2Ygd2hpY2ggc2F0dXJhdGVzPC90ZD5cbjx0ZD4wLjBnPC90ZD5cbjwvdHI+XG48dHI+XG48dGQ+Q2FyYm9oeWRyYXRlPC90ZD5cbjx0ZD42Mi45ZzwvdGQ+XG48L3RyPlxuPHRyPlxuPHRkPm9mIHdoaWNoIHN1Z2Fycyo8L3RkPlxuPHRkPjUyLjhnPC90ZD5cbjwvdHI+XG48dHI+XG48dGQ+UHJvdGVpbjwvdGQ+XG48dGQ+MC4xZzwvdGQ+XG48L3RyPlxuPHRyPlxuPHRkPlNhbHQ8L3RkPlxuPHRkPjAuMGc8L3RkPlxuPC90cj5cbjwvdGJvZHk+XG48L3RhYmxlPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5Nb3JlPC9oNT5cbjxwPlByb2R1Y3Qgb2bCoERvcnNldDxicj5TdWl0YWJsZSBmb3IgdmVnYW5zIGFuZCB2ZWdldGFyaWFuczxicj48L3A+XG48L2xpPiIsImRmYy1iOmhhc1F1YW50aXR5IjoiXzpiMTE5IiwiZGZjLWI6aW1hZ2UiOiJodHRwczovL2Nkbi5zaG9waWZ5LmNvbS9zL2ZpbGVzLzEvMDczMS84NDgzLzc5MzkvcHJvZHVjdHMvTGliZXJ0eS1GaWVsZHMtQXBwbGUtU3lydXAtMjAwbWxfYjFkMWU4Y2MtOTUzMC00MGZlLTk2ZDgtNWM1MmY2ZGE4YTAwLmpwZz92PTE2Nzc3NjA3NzQiLCJkZmMtYjpuYW1lIjoiQXBwbGUgU3lydXAgLSBDYXNlLCA2IHggMjUwbWwiLCJkZmMtYjpyZWZlcmVuY2VkQnkiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjY0MDIwOTkvQ2F0YWxvZ0l0ZW0ifSx7IkBpZCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjQwMjA5OS9DYXRhbG9nSXRlbSIsIkB0eXBlIjoiZGZjLWI6Q2F0YWxvZ0l0ZW0iLCJkZmMtYjpvZmZlcmVkVGhyb3VnaCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjQwMjA5OS9PZmZlciIsImRmYy1iOnNrdSI6IkxJQi9OQVNZUi9DNiIsImRmYy1iOnN0b2NrTGltaXRhdGlvbiI6Ii0xIn0seyJAaWQiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjY0MDIwOTkvT2ZmZXIiLCJAdHlwZSI6ImRmYy1iOk9mZmVyIiwiZGZjLWI6aGFzUHJpY2UiOnsiQGlkIjoiXzpiMTIwIn19XX0= - recorded_at: Fri, 02 Aug 2024 05:18:54 GMT + eyJAY29udGV4dCI6Imh0dHBzOi8vd3d3LmRhdGFmb29kY29uc29ydGl1bS5vcmciLCJAZ3JhcGgiOlt7IkBpZCI6Il86YjEzNyIsIkB0eXBlIjoiZGZjLWI6UXVhbnRpdGF0aXZlVmFsdWUiLCJkZmMtYjpoYXNVbml0IjoiZGZjLW06S2lsb2dyYW0iLCJkZmMtYjp2YWx1ZSI6IjAuMDQifSx7IkBpZCI6Il86YjEzOCIsIkB0eXBlIjoiZGZjLWI6UHJpY2UiLCJkZmMtYjpWQVRyYXRlIjoiMCIsImRmYy1iOmhhc1VuaXQiOiJkZmMtbTpFdXJvIiwiZGZjLWI6dmFsdWUiOiI2LjI1In0seyJAaWQiOiJfOmIxMzkiLCJAdHlwZSI6ImRmYy1iOlF1YW50aXRhdGl2ZVZhbHVlIiwiZGZjLWI6aGFzVW5pdCI6ImRmYy1tOktpbG9ncmFtIiwiZGZjLWI6dmFsdWUiOiIwLjI1In0seyJAaWQiOiJfOmIxNDAiLCJAdHlwZSI6ImRmYy1iOlByaWNlIiwiZGZjLWI6VkFUcmF0ZSI6IjAiLCJkZmMtYjpoYXNVbml0IjoiZGZjLW06RXVybyIsImRmYy1iOnZhbHVlIjoiNy4wMCJ9LHsiQGlkIjoiXzpiMTQxIiwiQHR5cGUiOiJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZSIsImRmYy1iOmhhc1VuaXQiOiJkZmMtbTpLaWxvZ3JhbSIsImRmYy1iOnZhbHVlIjoiMC4yNCJ9LHsiQGlkIjoiXzpiMTQyIiwiQHR5cGUiOiJkZmMtYjpQcmljZSIsImRmYy1iOlZBVHJhdGUiOiIwIiwiZGZjLWI6aGFzVW5pdCI6ImRmYy1tOkV1cm8iLCJkZmMtYjp2YWx1ZSI6IjMwLjIwIn0seyJAaWQiOiJfOmIxNDMiLCJAdHlwZSI6ImRmYy1iOlF1YW50aXRhdGl2ZVZhbHVlIiwiZGZjLWI6aGFzVW5pdCI6ImRmYy1tOktpbG9ncmFtIiwiZGZjLWI6dmFsdWUiOiIxLjUifSx7IkBpZCI6Il86YjE0NCIsIkB0eXBlIjoiZGZjLWI6UHJpY2UiLCJkZmMtYjpWQVRyYXRlIjoiMCIsImRmYy1iOmhhc1VuaXQiOiJkZmMtbTpFdXJvIiwiZGZjLWI6dmFsdWUiOiI3OS4wMCJ9LHsiQGlkIjoiXzpiMTQ1IiwiQHR5cGUiOiJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZSIsImRmYy1iOmhhc1VuaXQiOiJkZmMtbTpQaWVjZSIsImRmYy1iOnZhbHVlIjoiNiJ9LHsiQGlkIjoiXzpiMTQ2IiwiQHR5cGUiOiJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZSIsImRmYy1iOmhhc1VuaXQiOiJkZmMtbTpQaWVjZSIsImRmYy1iOnZhbHVlIjoiMSJ9LHsiQGlkIjoiXzpiMTQ3IiwiQHR5cGUiOiJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZSIsImRmYy1iOmhhc1VuaXQiOiJkZmMtbTpQaWVjZSIsImRmYy1iOnZhbHVlIjoiNiJ9LHsiQGlkIjoiXzpiMTQ4IiwiQHR5cGUiOiJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZSIsImRmYy1iOmhhc1VuaXQiOiJkZmMtbTpQaWVjZSIsImRmYy1iOnZhbHVlIjoiMSJ9LHsiQGlkIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2MjM4MjU5IiwiQHR5cGUiOiJkZmMtYjpTdXBwbGllZFByb2R1Y3QiLCJkZmMtYjpkZXNjcmlwdGlvbiI6Ijx0YWJsZSB3aWR0aD1cIjEwMCVcIj5cbjx0Ym9keT5cbjx0ciBzdHlsZT1cImJvcmRlcjogMHB4O1wiIGRhdGEtbWNlLXN0eWxlPVwiYm9yZGVyOiAwcHg7XCI+XG48dGQgYmdjb2xvcj1cIiNkNmZiZWRcIiBzdHlsZT1cImNvbG9yOiAjMDAwMDAwOyBib3JkZXI6IDBweDsgd2lkdGg6IDUyNnB4O1wiIGRhdGEtbWNlLXN0eWxlPVwiY29sb3I6ICMwMDAwMDA7IGJvcmRlcjogMHB4OyB3aWR0aDogNTI2cHg7XCI+PGI+VGhpcyByaWNoLCBpbnRlbnNlIGFuZCBkZWVwbHkgZmxhdm91cmVkIDYteWVhciBvbGQgYXBwbGUgYmFsc2FtaWMgdmluZWdhciBpcyBtYWRlIHVzaW5nIHRoZSB0cmFkaXRpb25hbCBJdGFsaWFuIG1ldGhvZCBvZiByZWR1Y3Rpb24gYW5kIGNvbmNlbnRyYXRpb24gb2YgdGhlIGp1aWNlIG92ZXIgYSBsZW5ndGh5IHBlcmlvZCBvZiB0aW1lLCByYXRoZXIgdGhhbiBieSBhZGRpbmcgZmxhdm91cmluZyBhbmQgY29sb3VyaW5nLsKgPC9iPjwvdGQ+XG48L3RyPlxuPC90Ym9keT5cbjwvdGFibGU+XG48cD5MaWJlcnR5IEZpZWxkcyBwcm9kdWNlIHNtYWxsIGJhdGNoZXMgb2Ygc3VwZXJiIHN5cnVwLCBiYWxzYW1pYyB2aW5lZ2FyLCBjaWRlciBhbmQgdm9ka2EgYnkgaGFuZCBmcm9tIHRoZSBmcnVpdCBvZiB0aGVpciBvd24gRG9yc2V0IGFwcGxlIG9yY2hhcmRzLCBwbGFudGVkIGZyb20gMjAxMC48YnI+PC9wPlxuPHA+VGhlIGJhbHNhbWljIHZpbmVnYXIgaXMgYWdlZCBmb3IgNiB5ZWFycyBpbiBiYXJyZWxzLsKgVGhlIG9ubHkgaW5ncmVkaWVudCBpcyBhcHBsZXMuPC9wPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5Ib3cgdG8gdXNlPC9oNT5cbjxwPlVzZSBsaWtlIEl0YWxpYW4gYmFsc2FtaWMgdmluZWdhci7CoDxzcGFuIGRhdGEtbWNlLWZyYWdtZW50PVwiMVwiPkFzIHdlbGwgYXMgdXNpbmcgb24gc2FsYWRzLCBpdOKAmXMgYSBncmVhdCBwYXJ0bmVyIGZvciBncmlsbGVkIG1lYXRzIG9yIGNoYXJjdXRlcmllOyBhIGRyb3AgYnJpbmdzIG91dCB0aGUgdGFzdGUgb2Ygc3RyYXdiZXJyaWVzIGFuZCBvdGhlciBzb2Z0IGZydWl0czsgYW5kIGl0IGNhbiByZWFsbHkgZW5oYW5jZSBhIHN0ZXcsIHNhdWNlIG9yIGEgc291cC7CoDwvc3Bhbj48L3A+XG48aDUgY2xhc3M9XCJwcm9kdWN0LWRldGFpbC10aXRsZVwiPlRvIHN0b3JlPGJyPlxuPC9oNT5cbjxwPkZvciBiZXN0IGJlZm9yZSBkYXRlIHNlZSBwYWNrLiBTdG9yZSBpbiBhIGNvb2wsIGRyeSBwbGFjZS48YnI+PC9wPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5JbmdyZWRpZW50czwvaDU+XG48cD5BcHBsZXM8YnI+PC9wPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5BbGxlcmd5IGluZm9ybWF0aW9uPC9oNT5cbjxwPk5vIGFsbGVyZ2Vucy48L3A+PGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5Nb3JlPC9oNT5cbjxwPlByb2R1Y3Qgb2bCoERvcnNldDxicj5TdWl0YWJsZSBmb3IgdmVnYW5zIGFuZCB2ZWdldGFyaWFuczxicj48L3A+IiwiZGZjLWI6aGFzUXVhbnRpdHkiOiJfOmIxMzciLCJkZmMtYjppbWFnZSI6Imh0dHBzOi8vY2RuLnNob3BpZnkuY29tL3MvZmlsZXMvMS8wNzMxLzg0ODMvNzkzOS9wcm9kdWN0cy9MaWJlcnR5LUZpZWxkcy1BcHBsZS1CYWxzYW1pYy1WaW5lZ2FyLTQwbWxfNzk2MTdlZWEtYWI4Yy00MDcwLTllNGQtNzExYmYwMzBhZDA3LmpwZz92PTE2Nzc3NjA3NzIiLCJkZmMtYjpuYW1lIjoiQXBwbGUgQmFsc2FtaWMgVmluZWdhciAtIFJldGFpbCBib3R0bGUsIDQwbWwiLCJkZmMtYjpyZWZlcmVuY2VkQnkiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjYyMzgyNTkvQ2F0YWxvZ0l0ZW0ifSx7IkBpZCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjIzODI1OS9Bc1BsYW5uZWRDb25zdW1wdGlvbkZsb3ciLCJAdHlwZSI6ImRmYy1iOkFzUGxhbm5lZENvbnN1bXB0aW9uRmxvdyIsImRmYy1iOmNvbnN1bWVzIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2MjM4MjU5IiwiZGZjLWI6aGFzUXVhbnRpdHkiOiJfOmIxNDUifSx7IkBpZCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjIzODI1OS9Bc1BsYW5uZWRQcm9kdWN0aW9uRmxvdyIsIkB0eXBlIjoiZGZjLWI6QXNQbGFubmVkUHJvZHVjdGlvbkZsb3ciLCJkZmMtYjpoYXNRdWFudGl0eSI6Il86YjE0NiIsImRmYy1iOnByb2R1Y2VzIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2MjcxMDI3In0seyJAaWQiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjYyMzgyNTkvQXNQbGFubmVkVHJhbnNmb3JtYXRpb24iLCJAdHlwZSI6ImRmYy1iOkFzUGxhbm5lZFRyYW5zZm9ybWF0aW9uIiwiZGZjLWI6aGFzSW5jb21lIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2MjM4MjU5L0FzUGxhbm5lZENvbnN1bXB0aW9uRmxvdyIsImRmYy1iOmhhc091dGNvbWUiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjYyMzgyNTkvQXNQbGFubmVkUHJvZHVjdGlvbkZsb3cifSx7IkBpZCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjIzODI1OS9DYXRhbG9nSXRlbSIsIkB0eXBlIjoiZGZjLWI6Q2F0YWxvZ0l0ZW0iLCJkZmMtYjpvZmZlcmVkVGhyb3VnaCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjIzODI1OS9PZmZlciIsImRmYy1iOnNrdSI6IkxJQi9OQUJWSS9CRiIsImRmYy1iOnN0b2NrTGltaXRhdGlvbiI6Ii0xIn0seyJAaWQiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjYyMzgyNTkvT2ZmZXIiLCJAdHlwZSI6ImRmYy1iOk9mZmVyIiwiZGZjLWI6aGFzUHJpY2UiOnsiQGlkIjoiXzpiMTM4In19LHsiQGlkIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2MjcxMDI3IiwiQHR5cGUiOiJkZmMtYjpTdXBwbGllZFByb2R1Y3QiLCJkZmMtYjpkZXNjcmlwdGlvbiI6Ijx0YWJsZSB3aWR0aD1cIjEwMCVcIj5cbjx0Ym9keT5cbjx0ciBzdHlsZT1cImJvcmRlcjogMHB4O1wiIGRhdGEtbWNlLXN0eWxlPVwiYm9yZGVyOiAwcHg7XCI+XG48dGQgYmdjb2xvcj1cIiNkNmZiZWRcIiBzdHlsZT1cImNvbG9yOiAjMDAwMDAwOyBib3JkZXI6IDBweDsgd2lkdGg6IDUyNnB4O1wiIGRhdGEtbWNlLXN0eWxlPVwiY29sb3I6ICMwMDAwMDA7IGJvcmRlcjogMHB4OyB3aWR0aDogNTI2cHg7XCI+PGI+VGhpcyByaWNoLCBpbnRlbnNlIGFuZCBkZWVwbHkgZmxhdm91cmVkIDYteWVhciBvbGQgYXBwbGUgYmFsc2FtaWMgdmluZWdhciBpcyBtYWRlIHVzaW5nIHRoZSB0cmFkaXRpb25hbCBJdGFsaWFuIG1ldGhvZCBvZiByZWR1Y3Rpb24gYW5kIGNvbmNlbnRyYXRpb24gb2YgdGhlIGp1aWNlIG92ZXIgYSBsZW5ndGh5IHBlcmlvZCBvZiB0aW1lLCByYXRoZXIgdGhhbiBieSBhZGRpbmcgZmxhdm91cmluZyBhbmQgY29sb3VyaW5nLsKgPC9iPjwvdGQ+XG48L3RyPlxuPC90Ym9keT5cbjwvdGFibGU+XG48cD5MaWJlcnR5IEZpZWxkcyBwcm9kdWNlIHNtYWxsIGJhdGNoZXMgb2Ygc3VwZXJiIHN5cnVwLCBiYWxzYW1pYyB2aW5lZ2FyLCBjaWRlciBhbmQgdm9ka2EgYnkgaGFuZCBmcm9tIHRoZSBmcnVpdCBvZiB0aGVpciBvd24gRG9yc2V0IGFwcGxlIG9yY2hhcmRzLCBwbGFudGVkIGZyb20gMjAxMC48YnI+PC9wPlxuPHA+VGhlIGJhbHNhbWljIHZpbmVnYXIgaXMgYWdlZCBmb3IgNiB5ZWFycyBpbiBiYXJyZWxzLsKgVGhlIG9ubHkgaW5ncmVkaWVudCBpcyBhcHBsZXMuPC9wPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5Ib3cgdG8gdXNlPC9oNT5cbjxwPlVzZSBsaWtlIEl0YWxpYW4gYmFsc2FtaWMgdmluZWdhci7CoDxzcGFuIGRhdGEtbWNlLWZyYWdtZW50PVwiMVwiPkFzIHdlbGwgYXMgdXNpbmcgb24gc2FsYWRzLCBpdOKAmXMgYSBncmVhdCBwYXJ0bmVyIGZvciBncmlsbGVkIG1lYXRzIG9yIGNoYXJjdXRlcmllOyBhIGRyb3AgYnJpbmdzIG91dCB0aGUgdGFzdGUgb2Ygc3RyYXdiZXJyaWVzIGFuZCBvdGhlciBzb2Z0IGZydWl0czsgYW5kIGl0IGNhbiByZWFsbHkgZW5oYW5jZSBhIHN0ZXcsIHNhdWNlIG9yIGEgc291cC7CoDwvc3Bhbj48L3A+XG48aDUgY2xhc3M9XCJwcm9kdWN0LWRldGFpbC10aXRsZVwiPlRvIHN0b3JlPGJyPlxuPC9oNT5cbjxwPkZvciBiZXN0IGJlZm9yZSBkYXRlIHNlZSBwYWNrLiBTdG9yZSBpbiBhIGNvb2wsIGRyeSBwbGFjZS48YnI+PC9wPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5JbmdyZWRpZW50czwvaDU+XG48cD5BcHBsZXM8YnI+PC9wPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5BbGxlcmd5IGluZm9ybWF0aW9uPC9oNT5cbjxwPk5vIGFsbGVyZ2Vucy48L3A+PGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5Nb3JlPC9oNT5cbjxwPlByb2R1Y3Qgb2bCoERvcnNldDxicj5TdWl0YWJsZSBmb3IgdmVnYW5zIGFuZCB2ZWdldGFyaWFuczxicj48L3A+IiwiZGZjLWI6aGFzUXVhbnRpdHkiOiJfOmIxNDEiLCJkZmMtYjppbWFnZSI6Imh0dHBzOi8vY2RuLnNob3BpZnkuY29tL3MvZmlsZXMvMS8wNzMxLzg0ODMvNzkzOS9wcm9kdWN0cy9MaWJlcnR5LUZpZWxkcy1BcHBsZS1CYWxzYW1pYy1WaW5lZ2FyLTQwbWxfNzk2MTdlZWEtYWI4Yy00MDcwLTllNGQtNzExYmYwMzBhZDA3LmpwZz92PTE2Nzc3NjA3NzIiLCJkZmMtYjpuYW1lIjoiQXBwbGUgQmFsc2FtaWMgVmluZWdhciAtIENhc2UsIDYgeCA0MG1sIiwiZGZjLWI6cmVmZXJlbmNlZEJ5IjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2MjcxMDI3L0NhdGFsb2dJdGVtIn0seyJAaWQiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjYyNzEwMjcvQ2F0YWxvZ0l0ZW0iLCJAdHlwZSI6ImRmYy1iOkNhdGFsb2dJdGVtIiwiZGZjLWI6b2ZmZXJlZFRocm91Z2giOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjYyNzEwMjcvT2ZmZXIiLCJkZmMtYjpza3UiOiJMSUIvTkFCVkkvQzYiLCJkZmMtYjpzdG9ja0xpbWl0YXRpb24iOiI1NSJ9LHsiQGlkIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2MjcxMDI3L09mZmVyIiwiQHR5cGUiOiJkZmMtYjpPZmZlciIsImRmYy1iOmhhc1ByaWNlIjp7IkBpZCI6Il86YjE0MiJ9fSx7IkBpZCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjM2OTMzMSIsIkB0eXBlIjoiZGZjLWI6U3VwcGxpZWRQcm9kdWN0IiwiZGZjLWI6ZGVzY3JpcHRpb24iOiI8dGFibGUgd2lkdGg9XCIxMDAlXCI+XG48dGJvZHk+XG48dHIgc3R5bGU9XCJib3JkZXI6IDBweDtcIiBkYXRhLW1jZS1zdHlsZT1cImJvcmRlcjogMHB4O1wiPlxuPHRkIHN0eWxlPVwiY29sb3I6ICMwMDAwMDA7IGJvcmRlcjogMHB4OyB3aWR0aDogNTI2cHg7XCIgYmdjb2xvcj1cIiNkNmZiZWRcIiBkYXRhLW1jZS1zdHlsZT1cImNvbG9yOiAjMDAwMDAwOyBib3JkZXI6IDBweDsgd2lkdGg6IDUyNnB4O1wiPjxiPkRvcnNldCdzIGFuc3dlciB0byBtYXBsZSBzeXJ1cCwgTGliZXJ0eSBGaWVsZHMnIEFwcGxlIFN5cnVwIGlzIGEgbHV4dXJpb3VzbHkgcmljaCBhbmQgaW50ZW5zZSBuYXR1cmFsIHN3ZWV0ZW5lciwgbWFkZSBvbmx5IGZyb20gdGhlIGFwcGxlcyBvZiB0aGVpciBvcmNoYXJkcy48L2I+PC90ZD5cbjwvdHI+XG48L3Rib2R5PlxuPC90YWJsZT5cbjxwPkxpYmVydHkgRmllbGRzIHByb2R1Y2Ugc21hbGwgYmF0Y2hlcyBvZiBzdXBlcmIgc3lydXAsIGJhbHNhbWljIHZpbmVnYXIsIGNpZGVyIGFuZCB2b2RrYSBieSBoYW5kIGZyb20gdGhlIGZydWl0IG9mIHRoZWlyIG93biBEb3JzZXQgYXBwbGUgb3JjaGFyZHMsIHBsYW50ZWQgZnJvbSAyMDEwLjxicj48L3A+XG48cD5UaGUgc3lydXAgY29udGFpbnMgbm8gYWRkaXRpdmVzLCBjaGVtaWNhbHMsIGZpbmluZyBhZ2VudHMgb3Igc3VnYXIuIE92ZXIgMmtnIGFwcGxlcyBnbyBpbnRvIGVhY2ggYm90dGxlLjwvcD5cbjxsaSBpZD1cInRhYjFcIiBjbGFzcz1cImFjdGl2ZVwiPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5Ib3cgdG8gdXNlPC9oNT5cbjxwPlVzZSBhcyBhIHN3ZWV0ZW5lciwgbGlrZSBtYXBsZSBzeXJ1cC7CoERlbGljaW91cyB3aXRoIHBhbmNha2VzLCB5b2dodXJ0IHBvcnJpZGdlLCBpY2UgY3JlYW0sIEZyZW5jaCB0b2FzdCBhbmQgbW9yZS4gTWl4IHdpdGggc3BhcmtsaW5nIHdhdGVyIG9yIGxlbW9uYWRlIHRvIG1ha2UgYSBub24tYWxjb2hvbGljIHN1bW1lciBjdXAsIG9yIHVzZSBhcyBhIHN3ZWV0ZW5lciBpbiBjb29raW5nLjwvcD5cbjxoNSBjbGFzcz1cInByb2R1Y3QtZGV0YWlsLXRpdGxlXCI+VG8gc3RvcmU8YnI+XG48L2g1PlxuPHA+Rm9yIGJlc3QgYmVmb3JlIGRhdGUgc2VlIHBhY2suIFN0b3JlIGluIGEgY29vbCwgZHJ5IHBsYWNlLiBSZWZyaWdlcmF0ZSBhZnRlciBvcGVuaW5nIGFuZCB1c2Ugd2l0aGluIDYgd2Vla3M8YnI+PC9wPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5JbmdyZWRpZW50czwvaDU+XG48cD5BcHBsZXM8YnI+PC9wPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5BbGxlcmd5IGluZm9ybWF0aW9uPC9oNT5cbjxwPk5vIGFsbGVyZ2Vucy4gRnJlZSBmcm9tIHN1bHBoaXRlcy48L3A+XG48dGFibGUgd2lkdGg9XCIxMDAlXCI+XG48dGJvZHk+XG48dHI+XG48dGQ+PHN0cm9uZz5UeXBpY2FsIHZhbHVlczwvc3Ryb25nPjwvdGQ+XG48dGQ+PHN0cm9uZz5QZXIgMTAwZzwvc3Ryb25nPjwvdGQ+XG48L3RyPlxuPHRyPlxuPHRkPkVuZXJneTwvdGQ+XG48dGQ+MTA3MWtKICgyNTJrY2FsKTwvdGQ+XG48L3RyPlxuPHRyPlxuPHRkPkZhdDwvdGQ+XG48dGQ+MC4wZzwvdGQ+XG48L3RyPlxuPHRyPlxuPHRkPm9mIHdoaWNoIHNhdHVyYXRlczwvdGQ+XG48dGQ+MC4wZzwvdGQ+XG48L3RyPlxuPHRyPlxuPHRkPkNhcmJvaHlkcmF0ZTwvdGQ+XG48dGQ+NjIuOWc8L3RkPlxuPC90cj5cbjx0cj5cbjx0ZD5vZiB3aGljaCBzdWdhcnMqPC90ZD5cbjx0ZD41Mi44ZzwvdGQ+XG48L3RyPlxuPHRyPlxuPHRkPlByb3RlaW48L3RkPlxuPHRkPjAuMWc8L3RkPlxuPC90cj5cbjx0cj5cbjx0ZD5TYWx0PC90ZD5cbjx0ZD4wLjBnPC90ZD5cbjwvdHI+XG48L3Rib2R5PlxuPC90YWJsZT5cbjxoNSBjbGFzcz1cInByb2R1Y3QtZGV0YWlsLXRpdGxlXCI+TW9yZTwvaDU+XG48cD5Qcm9kdWN0IG9mwqBEb3JzZXQ8YnI+U3VpdGFibGUgZm9yIHZlZ2FucyBhbmQgdmVnZXRhcmlhbnM8YnI+PC9wPlxuPC9saT4iLCJkZmMtYjpoYXNRdWFudGl0eSI6Il86YjEzOSIsImRmYy1iOmltYWdlIjoiaHR0cHM6Ly9jZG4uc2hvcGlmeS5jb20vcy9maWxlcy8xLzA3MzEvODQ4My83OTM5L3Byb2R1Y3RzL0xpYmVydHktRmllbGRzLUFwcGxlLVN5cnVwLTIwMG1sX2IxZDFlOGNjLTk1MzAtNDBmZS05NmQ4LTVjNTJmNmRhOGEwMC5qcGc/dj0xNjc3NzYwNzc0IiwiZGZjLWI6bmFtZSI6IkFwcGxlIFN5cnVwIC0gUmV0YWlsIGJvdHRsZSwgMjUwbWwiLCJkZmMtYjpyZWZlcmVuY2VkQnkiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjYzNjkzMzEvQ2F0YWxvZ0l0ZW0ifSx7IkBpZCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjM2OTMzMS9Bc1BsYW5uZWRDb25zdW1wdGlvbkZsb3ciLCJAdHlwZSI6ImRmYy1iOkFzUGxhbm5lZENvbnN1bXB0aW9uRmxvdyIsImRmYy1iOmNvbnN1bWVzIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2MzY5MzMxIiwiZGZjLWI6aGFzUXVhbnRpdHkiOiJfOmIxNDcifSx7IkBpZCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjM2OTMzMS9Bc1BsYW5uZWRQcm9kdWN0aW9uRmxvdyIsIkB0eXBlIjoiZGZjLWI6QXNQbGFubmVkUHJvZHVjdGlvbkZsb3ciLCJkZmMtYjpoYXNRdWFudGl0eSI6Il86YjE0OCIsImRmYy1iOnByb2R1Y2VzIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2NDAyMDk5In0seyJAaWQiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjYzNjkzMzEvQXNQbGFubmVkVHJhbnNmb3JtYXRpb24iLCJAdHlwZSI6ImRmYy1iOkFzUGxhbm5lZFRyYW5zZm9ybWF0aW9uIiwiZGZjLWI6aGFzSW5jb21lIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2MzY5MzMxL0FzUGxhbm5lZENvbnN1bXB0aW9uRmxvdyIsImRmYy1iOmhhc091dGNvbWUiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjYzNjkzMzEvQXNQbGFubmVkUHJvZHVjdGlvbkZsb3cifSx7IkBpZCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjM2OTMzMS9DYXRhbG9nSXRlbSIsIkB0eXBlIjoiZGZjLWI6Q2F0YWxvZ0l0ZW0iLCJkZmMtYjpvZmZlcmVkVGhyb3VnaCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjM2OTMzMS9PZmZlciIsImRmYy1iOnNrdSI6IkxJQi9OQVNZUi9CVCIsImRmYy1iOnN0b2NrTGltaXRhdGlvbiI6Ii0xIn0seyJAaWQiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjYzNjkzMzEvT2ZmZXIiLCJAdHlwZSI6ImRmYy1iOk9mZmVyIiwiZGZjLWI6aGFzUHJpY2UiOnsiQGlkIjoiXzpiMTQwIn19LHsiQGlkIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2NDAyMDk5IiwiQHR5cGUiOiJkZmMtYjpTdXBwbGllZFByb2R1Y3QiLCJkZmMtYjpkZXNjcmlwdGlvbiI6Ijx0YWJsZSB3aWR0aD1cIjEwMCVcIj5cbjx0Ym9keT5cbjx0ciBzdHlsZT1cImJvcmRlcjogMHB4O1wiIGRhdGEtbWNlLXN0eWxlPVwiYm9yZGVyOiAwcHg7XCI+XG48dGQgc3R5bGU9XCJjb2xvcjogIzAwMDAwMDsgYm9yZGVyOiAwcHg7IHdpZHRoOiA1MjZweDtcIiBiZ2NvbG9yPVwiI2Q2ZmJlZFwiIGRhdGEtbWNlLXN0eWxlPVwiY29sb3I6ICMwMDAwMDA7IGJvcmRlcjogMHB4OyB3aWR0aDogNTI2cHg7XCI+PGI+RG9yc2V0J3MgYW5zd2VyIHRvIG1hcGxlIHN5cnVwLCBMaWJlcnR5IEZpZWxkcycgQXBwbGUgU3lydXAgaXMgYSBsdXh1cmlvdXNseSByaWNoIGFuZCBpbnRlbnNlIG5hdHVyYWwgc3dlZXRlbmVyLCBtYWRlIG9ubHkgZnJvbSB0aGUgYXBwbGVzIG9mIHRoZWlyIG9yY2hhcmRzLjwvYj48L3RkPlxuPC90cj5cbjwvdGJvZHk+XG48L3RhYmxlPlxuPHA+TGliZXJ0eSBGaWVsZHMgcHJvZHVjZSBzbWFsbCBiYXRjaGVzIG9mIHN1cGVyYiBzeXJ1cCwgYmFsc2FtaWMgdmluZWdhciwgY2lkZXIgYW5kIHZvZGthIGJ5IGhhbmQgZnJvbSB0aGUgZnJ1aXQgb2YgdGhlaXIgb3duIERvcnNldCBhcHBsZSBvcmNoYXJkcywgcGxhbnRlZCBmcm9tIDIwMTAuPGJyPjwvcD5cbjxwPlRoZSBzeXJ1cCBjb250YWlucyBubyBhZGRpdGl2ZXMsIGNoZW1pY2FscywgZmluaW5nIGFnZW50cyBvciBzdWdhci4gT3ZlciAya2cgYXBwbGVzIGdvIGludG8gZWFjaCBib3R0bGUuPC9wPlxuPGxpIGlkPVwidGFiMVwiIGNsYXNzPVwiYWN0aXZlXCI+XG48aDUgY2xhc3M9XCJwcm9kdWN0LWRldGFpbC10aXRsZVwiPkhvdyB0byB1c2U8L2g1PlxuPHA+VXNlIGFzIGEgc3dlZXRlbmVyLCBsaWtlIG1hcGxlIHN5cnVwLsKgRGVsaWNpb3VzIHdpdGggcGFuY2FrZXMsIHlvZ2h1cnQgcG9ycmlkZ2UsIGljZSBjcmVhbSwgRnJlbmNoIHRvYXN0IGFuZCBtb3JlLiBNaXggd2l0aCBzcGFya2xpbmcgd2F0ZXIgb3IgbGVtb25hZGUgdG8gbWFrZSBhIG5vbi1hbGNvaG9saWMgc3VtbWVyIGN1cCwgb3IgdXNlIGFzIGEgc3dlZXRlbmVyIGluIGNvb2tpbmcuPC9wPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5UbyBzdG9yZTxicj5cbjwvaDU+XG48cD5Gb3IgYmVzdCBiZWZvcmUgZGF0ZSBzZWUgcGFjay4gU3RvcmUgaW4gYSBjb29sLCBkcnkgcGxhY2UuIFJlZnJpZ2VyYXRlIGFmdGVyIG9wZW5pbmcgYW5kIHVzZSB3aXRoaW4gNiB3ZWVrczxicj48L3A+XG48aDUgY2xhc3M9XCJwcm9kdWN0LWRldGFpbC10aXRsZVwiPkluZ3JlZGllbnRzPC9oNT5cbjxwPkFwcGxlczxicj48L3A+XG48aDUgY2xhc3M9XCJwcm9kdWN0LWRldGFpbC10aXRsZVwiPkFsbGVyZ3kgaW5mb3JtYXRpb248L2g1PlxuPHA+Tm8gYWxsZXJnZW5zLiBGcmVlIGZyb20gc3VscGhpdGVzLjwvcD5cbjx0YWJsZSB3aWR0aD1cIjEwMCVcIj5cbjx0Ym9keT5cbjx0cj5cbjx0ZD48c3Ryb25nPlR5cGljYWwgdmFsdWVzPC9zdHJvbmc+PC90ZD5cbjx0ZD48c3Ryb25nPlBlciAxMDBnPC9zdHJvbmc+PC90ZD5cbjwvdHI+XG48dHI+XG48dGQ+RW5lcmd5PC90ZD5cbjx0ZD4xMDcxa0ogKDI1MmtjYWwpPC90ZD5cbjwvdHI+XG48dHI+XG48dGQ+RmF0PC90ZD5cbjx0ZD4wLjBnPC90ZD5cbjwvdHI+XG48dHI+XG48dGQ+b2Ygd2hpY2ggc2F0dXJhdGVzPC90ZD5cbjx0ZD4wLjBnPC90ZD5cbjwvdHI+XG48dHI+XG48dGQ+Q2FyYm9oeWRyYXRlPC90ZD5cbjx0ZD42Mi45ZzwvdGQ+XG48L3RyPlxuPHRyPlxuPHRkPm9mIHdoaWNoIHN1Z2Fycyo8L3RkPlxuPHRkPjUyLjhnPC90ZD5cbjwvdHI+XG48dHI+XG48dGQ+UHJvdGVpbjwvdGQ+XG48dGQ+MC4xZzwvdGQ+XG48L3RyPlxuPHRyPlxuPHRkPlNhbHQ8L3RkPlxuPHRkPjAuMGc8L3RkPlxuPC90cj5cbjwvdGJvZHk+XG48L3RhYmxlPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5Nb3JlPC9oNT5cbjxwPlByb2R1Y3Qgb2bCoERvcnNldDxicj5TdWl0YWJsZSBmb3IgdmVnYW5zIGFuZCB2ZWdldGFyaWFuczxicj48L3A+XG48L2xpPiIsImRmYy1iOmhhc1F1YW50aXR5IjoiXzpiMTQzIiwiZGZjLWI6aW1hZ2UiOiJodHRwczovL2Nkbi5zaG9waWZ5LmNvbS9zL2ZpbGVzLzEvMDczMS84NDgzLzc5MzkvcHJvZHVjdHMvTGliZXJ0eS1GaWVsZHMtQXBwbGUtU3lydXAtMjAwbWxfYjFkMWU4Y2MtOTUzMC00MGZlLTk2ZDgtNWM1MmY2ZGE4YTAwLmpwZz92PTE2Nzc3NjA3NzQiLCJkZmMtYjpuYW1lIjoiQXBwbGUgU3lydXAgLSBDYXNlLCA2IHggMjUwbWwiLCJkZmMtYjpyZWZlcmVuY2VkQnkiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjY0MDIwOTkvQ2F0YWxvZ0l0ZW0ifSx7IkBpZCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjQwMjA5OS9DYXRhbG9nSXRlbSIsIkB0eXBlIjoiZGZjLWI6Q2F0YWxvZ0l0ZW0iLCJkZmMtYjpvZmZlcmVkVGhyb3VnaCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjQwMjA5OS9PZmZlciIsImRmYy1iOnNrdSI6IkxJQi9OQVNZUi9DNiIsImRmYy1iOnN0b2NrTGltaXRhdGlvbiI6Ii0xIn0seyJAaWQiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjY0MDIwOTkvT2ZmZXIiLCJAdHlwZSI6ImRmYy1iOk9mZmVyIiwiZGZjLWI6aGFzUHJpY2UiOnsiQGlkIjoiXzpiMTQ0In19XX0= + recorded_at: Fri, 02 Aug 2024 05:35:56 GMT recorded_with: VCR 6.2.0 From da246380794894e704c60b59fc3010c690135dfd Mon Sep 17 00:00:00 2001 From: EdwardLi-coder <2023edwardll@gmail.com> Date: Sat, 10 Aug 2024 22:04:17 +0800 Subject: [PATCH 015/206] update artifact v3 to v4 --- .github/workflows/build.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 03d98ef5e8..c9d616ec0e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -216,7 +216,7 @@ jobs: - name: Archive failed tests screenshots if: failure() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: failed-tests-screenshots path: tmp/capybara/screenshots/*.png @@ -294,7 +294,7 @@ jobs: - name: Archive failed tests screenshots if: failure() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: failed-tests-screenshots path: tmp/capybara/screenshots/*.png @@ -373,7 +373,7 @@ jobs: - name: Archive failed tests screenshots if: failure() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: failed-tests-screenshots path: tmp/capybara/screenshots/*.png From af7b6633344bfeebe51673ccd6e2b013def9bb20 Mon Sep 17 00:00:00 2001 From: EdwardLi-coder <2023edwardll@gmail.com> Date: Sat, 10 Aug 2024 23:55:58 +0800 Subject: [PATCH 016/206] update admin_style_v3-for-75% --- ...40810150912_activate_admin_style_v3_for75_pc_users.rb | 9 +++++++++ db/schema.rb | 2 +- lib/open_food_network/feature_toggle.rb | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 db/migrate/20240810150912_activate_admin_style_v3_for75_pc_users.rb diff --git a/db/migrate/20240810150912_activate_admin_style_v3_for75_pc_users.rb b/db/migrate/20240810150912_activate_admin_style_v3_for75_pc_users.rb new file mode 100644 index 0000000000..cc412c0b58 --- /dev/null +++ b/db/migrate/20240810150912_activate_admin_style_v3_for75_pc_users.rb @@ -0,0 +1,9 @@ +class ActivateAdminStyleV3For75PcUsers < ActiveRecord::Migration[7.0] + def up + Flipper.enable_percentage_of_actors(:admin_style_v3, 75) + end + + def down + Flipper.enable_percentage_of_actors(:admin_style_v3, 50) + end +end diff --git a/db/schema.rb b/db/schema.rb index 04013a7caf..0ba99991e3 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2024_08_01_034710) do +ActiveRecord::Schema[7.0].define(version: 2024_08_10_150912) do # These are extensions that must be enabled in order to support this database enable_extension "pg_stat_statements" enable_extension "plpgsql" diff --git a/lib/open_food_network/feature_toggle.rb b/lib/open_food_network/feature_toggle.rb index aedef176fb..64a5d75dc9 100644 --- a/lib/open_food_network/feature_toggle.rb +++ b/lib/open_food_network/feature_toggle.rb @@ -51,7 +51,7 @@ module OpenFoodNetwork # Copy features here that were activated in a migration so that new # instances, development and test environments have the feature active. "admin_style_v3" => <<~DESC, - Test the work-in-progress design updates. + Test the work-in-progress design updates. Activated for admins, new users, and 75% of all users. DESC }.freeze From be13d43e0cbb0398620e22ab445089c64ee6bc1c Mon Sep 17 00:00:00 2001 From: EdwardLi-coder <2023edwardll@gmail.com> Date: Sun, 11 Aug 2024 00:20:18 +0800 Subject: [PATCH 017/206] delete Archive failed tests screenshots --- .github/workflows/build.yml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c9d616ec0e..5ca5ebb543 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -371,15 +371,6 @@ jobs: run: | bin/rake knapsack_pro:rspec - - name: Archive failed tests screenshots - if: failure() - uses: actions/upload-artifact@v4 - with: - name: failed-tests-screenshots - path: tmp/capybara/screenshots/*.png - retention-days: 7 - if-no-files-found: ignore - test_the_rest: runs-on: ubuntu-22.04 services: From 97a72dfde7920e64ec3bfd8c26a0098a88edb35a Mon Sep 17 00:00:00 2001 From: EdwardLi-coder <2023edwardll@gmail.com> Date: Mon, 12 Aug 2024 12:01:02 +0800 Subject: [PATCH 018/206] change colour of "complete order" --- app/webpacker/css/darkswarm/split-checkout.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/webpacker/css/darkswarm/split-checkout.scss b/app/webpacker/css/darkswarm/split-checkout.scss index 1fc90b9815..2c947c5cdc 100644 --- a/app/webpacker/css/darkswarm/split-checkout.scss +++ b/app/webpacker/css/darkswarm/split-checkout.scss @@ -347,7 +347,7 @@ margin-top: 40px; .button.primary { - background-color: $clr-turquoise; + background-color: $orange-500; &:hover { text-shadow: 0 1px 0 rgba(0, 0, 0, 0.2); } From 6ad03e6d5c035740a91576fdfc3e093b1854aa59 Mon Sep 17 00:00:00 2001 From: David Cook Date: Mon, 12 Aug 2024 17:49:09 +1000 Subject: [PATCH 019/206] Remove comment --- lib/open_food_network/feature_toggle.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/open_food_network/feature_toggle.rb b/lib/open_food_network/feature_toggle.rb index 64a5d75dc9..aedef176fb 100644 --- a/lib/open_food_network/feature_toggle.rb +++ b/lib/open_food_network/feature_toggle.rb @@ -51,7 +51,7 @@ module OpenFoodNetwork # Copy features here that were activated in a migration so that new # instances, development and test environments have the feature active. "admin_style_v3" => <<~DESC, - Test the work-in-progress design updates. Activated for admins, new users, and 75% of all users. + Test the work-in-progress design updates. DESC }.freeze From 9fe128d494e5addbc3bce39fbb328ea7cc4d13aa Mon Sep 17 00:00:00 2001 From: EdwardLi-coder <2023edwardll@gmail.com> Date: Mon, 12 Aug 2024 16:04:22 +0800 Subject: [PATCH 020/206] add fail test --- spec/system/admin/products_v3/update_spec.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/spec/system/admin/products_v3/update_spec.rb b/spec/system/admin/products_v3/update_spec.rb index fec4236177..8537103d09 100644 --- a/spec/system/admin/products_v3/update_spec.rb +++ b/spec/system/admin/products_v3/update_spec.rb @@ -682,6 +682,12 @@ RSpec.describe 'As an enterprise user, I can update my products' do end end + it "fails intentionally to generate screenshot" do + within ".reveal-modal" do + expect(page).to have_content "This text does not exist" + end + end + it 'shows a modal telling not a valid image when uploading a non valid image file' do within ".reveal-modal" do attach_file 'image[attachment]', From 0de8a90b14c5d5e714c63dcf5f9fb02e2989f0b5 Mon Sep 17 00:00:00 2001 From: wandji20 Date: Tue, 9 Jul 2024 18:20:36 +0100 Subject: [PATCH 021/206] Add warning modal to order cycle with attached schedule general setting form [OFN-11613] --- .../services/order_cycle.js.coffee | 2 + .../admin/order_cycles_controller.rb | 7 +- .../_name_and_timing_form.html.haml | 4 +- app/views/admin/order_cycles/edit.html.haml | 45 +++++++-- .../controllers/order_cycle_controller.js | 52 +++++++++++ app/webpacker/css/admin/order_cycles.scss | 22 +++++ config/locales/en.yml | 5 + spec/system/admin/order_cycles/edit_spec.rb | 91 +++++++++++++++++++ 8 files changed, 216 insertions(+), 12 deletions(-) create mode 100644 app/webpacker/controllers/order_cycle_controller.js create mode 100644 spec/system/admin/order_cycles/edit_spec.rb diff --git a/app/assets/javascripts/admin/order_cycles/services/order_cycle.js.coffee b/app/assets/javascripts/admin/order_cycles/services/order_cycle.js.coffee index 88ad9da1b8..cb8e9e059e 100644 --- a/app/assets/javascripts/admin/order_cycles/services/order_cycle.js.coffee +++ b/app/assets/javascripts/admin/order_cycles/services/order_cycle.js.coffee @@ -167,6 +167,8 @@ angular.module('admin.orderCycles').factory 'OrderCycle', ($resource, $window, $ if destination? $window.location = destination else + if ($window.adminOrderCycleUpdateCallback) + adminOrderCycleUpdateCallback(data.order_cycle); StatusMessage.display 'success', t('js.order_cycles.update_success') , (response) -> if response.data.errors? diff --git a/app/controllers/admin/order_cycles_controller.rb b/app/controllers/admin/order_cycles_controller.rb index 35333e7af7..a52c40f7a4 100644 --- a/app/controllers/admin/order_cycles_controller.rb +++ b/app/controllers/admin/order_cycles_controller.rb @@ -70,7 +70,12 @@ module Admin respond_to do |format| flash[:success] = t('.success') if params[:reloading] == '1' format.html { redirect_to_after_update_path } - format.json { render json: { success: true } } + format.json { + render json: { success: true, order_cycle: { + orders_open_at: @order_cycle.orders_open_at&.strftime('%Y-%m-%d %H:%M'), + orders_close_at: @order_cycle.orders_close_at&.strftime('%Y-%m-%d %H:%M') + } } + } end elsif request.format.html? render :checkout_options diff --git a/app/views/admin/order_cycles/_name_and_timing_form.html.haml b/app/views/admin/order_cycles/_name_and_timing_form.html.haml index e3bc8df191..008b5e6178 100644 --- a/app/views/admin/order_cycles/_name_and_timing_form.html.haml +++ b/app/views/admin/order_cycles/_name_and_timing_form.html.haml @@ -11,7 +11,7 @@ = f.label :orders_open_at, t('.orders_open') .omega.six.columns.fullwidth_inputs - if viewing_as_coordinator_of?(@order_cycle) - = f.text_field :orders_open_at, data: { controller: "flatpickr", "flatpickr-enable-time-value": true }, 'ng-model' => 'order_cycle.orders_open_at', 'ng-if' => 'loaded()', 'change-warning' => 'order_cycle', class: "datetimepicker" + = f.text_field :orders_open_at, data: { controller: "flatpickr", "flatpickr-enable-time-value": true, action: 'order-cycle#toggleSaveBtns', 'order-cycle-target': 'input' }, 'ng-model' => 'order_cycle.orders_open_at', 'ng-if' => 'loaded()', 'change-warning' => 'order_cycle', class: "datetimepicker" - else {{ order_cycle.orders_open_at }} @@ -24,7 +24,7 @@ = f.label :orders_close, t('.orders_close') .six.columns.omega.fullwidth_inputs - if viewing_as_coordinator_of?(@order_cycle) - = f.text_field :orders_close_at, data: { controller: "flatpickr", "flatpickr-enable-time-value": true }, 'ng-model' => 'order_cycle.orders_close_at', 'ng-if' => 'loaded()', 'change-warning' => 'order_cycle', class: "datetimepicker" + = f.text_field :orders_close_at, data: { controller: "flatpickr", "flatpickr-enable-time-value": true, action: 'order-cycle#toggleSaveBtns', 'order-cycle-target': 'input' }, 'ng-model' => 'order_cycle.orders_close_at', 'ng-if' => 'loaded()', 'change-warning' => 'order_cycle', class: "datetimepicker" - else {{ order_cycle.orders_close_at }} diff --git a/app/views/admin/order_cycles/edit.html.haml b/app/views/admin/order_cycles/edit.html.haml index f6f3bc6f49..44fe2afd42 100644 --- a/app/views/admin/order_cycles/edit.html.haml +++ b/app/views/admin/order_cycles/edit.html.haml @@ -15,19 +15,46 @@ = t :edit_order_cycle - ng_controller = @order_cycle.simple? ? 'AdminSimpleEditOrderCycleCtrl' : 'AdminEditOrderCycleCtrl' +- has_scheduled_order = @order_cycle.schedules.exists? = admin_inject_order_cycle_instance(@order_cycle) -= form_for [main_app, :admin, @order_cycle], :url => '', :html => {:class => 'ng order_cycle', 'ng-app' => 'admin.orderCycles', 'ng-controller' => ng_controller, name: 'order_cycle_form'} do |f| - += form_for [main_app, :admin, @order_cycle], :url => '', :html => {:class => 'ng order_cycle', 'ng-app' => 'admin.orderCycles', 'ng-controller' => ng_controller, name: 'order_cycle_form', data: { controller: 'modal modal-link order-cycle', "modal-link-target-value": "linked-schedule-warning-modal", 'order-cycle-has-schedule-value': has_scheduled_order, 'order-cycle-init-vals-value': { 'order_cycle[orders_open_at]': @order_cycle.orders_open_at&.strftime('%Y-%m-%d %H:%M'), 'order_cycle[orders_close_at]': @order_cycle.orders_close_at&.strftime('%Y-%m-%d %H:%M') } } } do |f| %save-bar{ dirty: "order_cycle_form.$dirty", persist: "true" } - %input.red{ type: "button", value: t('.save'), "ng-click": "submit($event, null)", "ng-disabled": "!order_cycle_form.$dirty || order_cycle_form.$invalid" } - - if @order_cycle.simple? - %input.red{ type: "button", value: t('.save_and_back_to_list'), "ng-click": "submit($event, '#{main_app.admin_order_cycles_path}')", "ng-disabled": "!order_cycle_form.$dirty || order_cycle_form.$invalid" } - - else - %input.red{ type: "button", value: t('.save_and_next'), "ng-click": "submit($event, '#{main_app.admin_order_cycle_incoming_path(@order_cycle)}')", "ng-disabled": "!order_cycle_form.$dirty || order_cycle_form.$invalid" } - %input{ type: "button", value: t('.next'), "ng-click": "cancel('#{main_app.admin_order_cycle_incoming_path(@order_cycle)}')", "ng-disabled": "order_cycle_form.$dirty" } - %input{ type: "button", "ng-value": "order_cycle_form.$dirty ? '#{t('.cancel')}' : '#{t('.back_to_list')}'", "ng-click": "cancel('#{main_app.admin_order_cycles_path}')" } + %div#form-actions + %input.red{ type: "button", value: t('.save'), "ng-click": "submit($event, null)", "ng-disabled": "!order_cycle_form.$dirty || order_cycle_form.$invalid" } + - if @order_cycle.simple? + %input.red{ type: "button", value: t('.save_and_back_to_list'), "ng-click": "submit($event, '#{main_app.admin_order_cycles_path}')", "ng-disabled": "!order_cycle_form.$dirty || order_cycle_form.$invalid" } + - else + %input.red{ type: "button", value: t('.save_and_next'), "ng-click": "submit($event, '#{main_app.admin_order_cycle_incoming_path(@order_cycle)}')", "ng-disabled": "!order_cycle_form.$dirty || order_cycle_form.$invalid" } + %input{ type: "button", value: t('.next'), "ng-click": "cancel('#{main_app.admin_order_cycle_incoming_path(@order_cycle)}')", "ng-disabled": "order_cycle_form.$dirty" } + %input{ type: "button", "ng-value": "order_cycle_form.$dirty ? '#{t('.cancel')}' : '#{t('.back_to_list')}'", "ng-click": "cancel('#{main_app.admin_order_cycles_path}')" } + %div#modal-actions{style: "display: none;"} + %input.red{ type: "button", value: t('.save'), "ng-disabled": "!order_cycle_form.$dirty || order_cycle_form.$invalid", data: { 'action': 'click->modal-link#open click->order-cycle#updateModalConfirmButton', 'target': 'save'} } + - if @order_cycle.simple? + %input.red{ type: "button", value: t('.save_and_back_to_list'), "ng-disabled": "!order_cycle_form.$dirty || order_cycle_form.$invalid", data: { 'action': 'click->modal-link#open click->order-cycle#updateModalConfirmButton', 'target': 'saveAndBack'} } + - else + %input.red{ type: "button", value: t('.save_and_next'), "ng-disabled": "!order_cycle_form.$dirty || order_cycle_form.$invalid", data: { 'action': 'click->modal-link#open click->order-cycle#updateModalConfirmButton', 'target': 'saveAndNext'} } + %input{ type: "button", value: t('.next'), "ng-click": "cancel('#{main_app.admin_order_cycle_incoming_path(@order_cycle)}')", "ng-disabled": "order_cycle_form.$dirty" } + %input{ type: "button", "ng-value": "order_cycle_form.$dirty ? '#{t('.cancel')}' : '#{t('.back_to_list')}'", "ng-click": "cancel('#{main_app.admin_order_cycles_path}')" } - if @order_cycle.simple? = render 'simple_form', f: f - else = render 'form', f: f + + - if has_scheduled_order + = render ModalComponent.new(id: "linked-schedule-warning-modal", close_button: false) do + .content + .modal-body + %h6 + = t('admin.order_cycles.edit.linked_schedule_warning_modal.title') + %div{ style: 'font-size: 1rem;' } + = t('admin.order_cycles.edit.linked_schedule_warning_modal.content') + %p.modal-actions.justify-end + %button.button.secondary#modal-confirm{ type: "button", 'data-action': 'click->modal#close', style: 'display: none;', data: { 'order-cycle-target': 'modalConfirm', request: 'save' }, "ng-click": "submit($event, null)" } + = t('admin.order_cycles.edit.linked_schedule_warning_modal.proceed') + %button.button.secondary#modal-confirm{ type: "button", 'data-action': 'click->modal#close', style: 'display: none;', data: { 'order-cycle-target': 'modalConfirm', request: 'saveAndNext' }, "ng-click": "submit($event, '#{main_app.admin_order_cycle_incoming_path(@order_cycle)}')" } + = t('admin.order_cycles.edit.linked_schedule_warning_modal.proceed') + %button.button.secondary#modal-confirm{ type: "button", 'data-action': 'click->modal#close', style: 'display: none;', data: { 'order-cycle-target': 'modalConfirm', request: 'saveAndBack' }, "ng-click": "submit($event, '#{main_app.admin_order_cycles_path}')" } + = t('admin.order_cycles.edit.linked_schedule_warning_modal.proceed') + %button.button.primary{ type: "button", 'data-action': 'click->modal#close' } + = t('admin.order_cycles.edit.linked_schedule_warning_modal.cancel') \ No newline at end of file diff --git a/app/webpacker/controllers/order_cycle_controller.js b/app/webpacker/controllers/order_cycle_controller.js new file mode 100644 index 0000000000..5bb7ecdae0 --- /dev/null +++ b/app/webpacker/controllers/order_cycle_controller.js @@ -0,0 +1,52 @@ +import { Controller } from "stimulus"; + +export default class extends Controller { + static targets = ['input', 'modalConfirm']; + static values = { initVals: { type: Object, default: {} }, hasSchedule: { type: Boolean, default: false } }; + + connect() { + if(!this.hasScheduleValue) return; + // Attach update callback method + window.adminOrderCycleUpdateCallback = this.updateCallback.bind(this); + } + + toggleSaveBtns() { + if(!this.hasScheduleValue) return; + + // Check that datetime input value has a change + const dirty = this.inputTargets.some(ele => + new Date(this.initValsValue[`${ele.name}`]).getTime() !== new Date(ele.value).getTime()); + + // Toggle save bar action button + if (dirty) { + this.element.querySelector('#form-actions').style.display = 'none'; + this.element.querySelector('#modal-actions').style.display = 'unset'; + } else { + this.element.querySelector('#form-actions').style.display = 'unset'; + this.element.querySelector('#modal-actions').style.display = 'none'; + } + } + + updateModalConfirmButton(e) { + if(!this.hasScheduleValue) return; + // Display modal confirm button coresponding to save bar button clicked + this.modalConfirmTargets.forEach(ele => { + if (e.target.getAttribute('data-target') === ele.getAttribute('data-request')) { + ele.style.display = 'unset'; + } else { + ele.style.display = 'none'; + } + }); + } + + updateCallback(data) { + // Reset order values and update save bar buttons + this.initValsValue = { 'order_cycle[orders_open_at]': data.orders_open_at, 'order_cycle[orders_close_at]': data.orders_close_at }; + this.toggleSaveBtns(); + } + + disconnect() { + // remove attached update callback method + delete window.adminOrderCycleUpdateCallback; + } +} \ No newline at end of file diff --git a/app/webpacker/css/admin/order_cycles.scss b/app/webpacker/css/admin/order_cycles.scss index ea73266fb8..cad5d3afb6 100644 --- a/app/webpacker/css/admin/order_cycles.scss +++ b/app/webpacker/css/admin/order_cycles.scss @@ -62,3 +62,25 @@ form.order_cycle { } } } + +#linked-schedule-warning-modal { + .reveal-modal { + width: 28rem; + + .content { + display: flex; + flex-direction: column; + gap: 2rem; + + .modal-body { + display: flex; + flex-direction: column; + gap: 1rem; + } + + .modal-actions { + gap: 1rem; + } + } + } +} diff --git a/config/locales/en.yml b/config/locales/en.yml index 74e5b24b68..8e1e1ad995 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1506,6 +1506,11 @@ en: choose_products_from: "Choose Products From:" re_notify_producers: Re notify producers notify_producers_tip: This will send an email to each producer with the list of their orders. + linked_schedule_warning_modal: + title: 'Orders are linked to this order cycle.' + content: 'If you wish to create a new order cycle, it is recommended to duplicate the order cycle first and then change the dates.' + proceed: 'Proceed anyway' + cancel: 'Cancel' incoming: incoming: "Incoming" supplier: "Supplier" diff --git a/spec/system/admin/order_cycles/edit_spec.rb b/spec/system/admin/order_cycles/edit_spec.rb new file mode 100644 index 0000000000..ff157e5539 --- /dev/null +++ b/spec/system/admin/order_cycles/edit_spec.rb @@ -0,0 +1,91 @@ +# frozen_string_literal: true + +require 'system_helper' + +RSpec.describe ' + As an administrator + I want to edit a specific order cycle +' do + include AdminHelper + include AuthenticationHelper + include WebHelper + + let(:oc0) { + create(:simple_order_cycle, name: 'oc0', + orders_open_at: nil, orders_close_at: nil) + } + let(:oc1) { create(:order_cycle, name: 'oc1') } + + context 'when cycle has attached schedule(s)' do + it "properly toggles order cycle save bar buttons to show warning modal" do + create(:schedule, name: 'Schedule1', order_cycles: [oc0]) + + # When I go to the admin order cycle edit page + login_as_admin + visit edit_admin_order_cycle_path(oc0) + + expect(page).to have_selector("#linked-schedule-warning-modal") + expect(page).not_to have_selector("#modal-actions") + expect(page).to have_selector("#form-actions") + + # change non-date range field + fill_in 'order_cycle_name', with: "OC0 name updated" + expect(page).to have_content('You have unsaved changes') + click_button('Save') + expect(page).not_to have_selector('#linked-schedule-warning-modal .reveal-modal.in') + expect(page).to have_content('Your order cycle has been updated.') + + # change date range field value + time = DateTime.current + find('#order_cycle_orders_close_at').click + select_datetime_from_datepicker Time.zone.at(time) + + # Enable savebar save buttons to open warning modal + expect(page.find('#order_cycle_orders_close_at').value).to eq time.strftime('%Y-%m-%d %H:%M') + expect(page).not_to have_selector("#form-actions") + expect(page).to have_selector("#modal-actions") + expect(page).to have_content('You have unsaved changes') + expect(page).not_to have_selector('#linked-schedule-warning-modal .reveal-modal.in') + + # click save to open warning modal + click_button('Save') + expect(page).to have_selector('#linked-schedule-warning-modal .reveal-modal.in') + + # confirm to close modal and update order cycle changed fields + click_button('Proceed anyway') + expect(page).not_to have_selector('#linked-schedule-warning-modal .reveal-modal.in') + expect(page.find('#order_cycle_orders_close_at').value).to eq time.strftime('%Y-%m-%d %H:%M') + end + end + + context 'when cycle does not have attached schedule' do + it "does not render warning modal" do + # When I go to the admin order cycle edit page + login_as_admin + visit edit_admin_order_cycle_path(oc1) + + expect(page).not_to have_selector("#linked-schedule-warning-modal") + expect(page).not_to have_selector("#modal-actions") + expect(page).to have_selector("#form-actions") + + # change non-date range field value + fill_in 'order_cycle_name', with: "OC1 name updated" + expect(page).to have_content('You have unsaved changes') + + # click save + click_button('Save') + expect(page).not_to have_selector('#linked-schedule-warning-modal .reveal-modal.in') + expect(page).to have_content('Your order cycle has been updated.') + + # change date range field value + time = DateTime.current + find('#order_cycle_orders_close_at').click + select_datetime_from_datepicker Time.zone.at(time) + expect(page).to have_content('You have unsaved changes') + + click_button('Save') + expect(page).not_to have_selector("#modal-actions") + expect(page).to have_content('Your order cycle has been updated.') + end + end +end From 91fddeaa8b62cf4d88d621dfb4cd2a9ea5e72076 Mon Sep 17 00:00:00 2001 From: wandji20 Date: Tue, 9 Jul 2024 23:16:46 +0100 Subject: [PATCH 022/206] Fix failing spec [OFN-11613] --- app/webpacker/controllers/mixins/useOpenAndCloseAsAModal.js | 3 +++ spec/system/admin/subscriptions/smoke_tests_spec.rb | 5 +++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app/webpacker/controllers/mixins/useOpenAndCloseAsAModal.js b/app/webpacker/controllers/mixins/useOpenAndCloseAsAModal.js index 3c9e3d7b3d..7648563791 100644 --- a/app/webpacker/controllers/mixins/useOpenAndCloseAsAModal.js +++ b/app/webpacker/controllers/mixins/useOpenAndCloseAsAModal.js @@ -12,6 +12,9 @@ export const useOpenAndCloseAsAModal = (controller) => { }.bind(controller), close: function (_event, remove = false) { + // Only execute close if there is an open modal + if (!document.querySelector("body").classList.contains('modal-open')) return; + this.modalTarget.classList.remove("in"); this.backgroundTarget.classList.remove("in"); document.querySelector("body").classList.remove("modal-open"); diff --git a/spec/system/admin/subscriptions/smoke_tests_spec.rb b/spec/system/admin/subscriptions/smoke_tests_spec.rb index 19f224f41d..837555b9ff 100644 --- a/spec/system/admin/subscriptions/smoke_tests_spec.rb +++ b/spec/system/admin/subscriptions/smoke_tests_spec.rb @@ -125,8 +125,9 @@ RSpec.describe 'Subscriptions' do select_datetime_from_datepicker Time.zone.at(1.month.from_now) find("body").send_keys(:escape) - click_button 'Save' - + # Click save and comfirm in warning modal (because date time range value was changed) + click_button('Save') + click_button('Proceed anyway') visit edit_admin_subscription_path(subscription) click_button 'edit-products' From ea238829a80f0698045b6ad0beda4d8704d4ffeb Mon Sep 17 00:00:00 2001 From: wandji20 Date: Wed, 24 Jul 2024 20:06:25 +0100 Subject: [PATCH 023/206] Revert front end validation and implement backend validation for changes in datetime order cycle values [OFN-11613] --- .../order_cycles/controllers/edit.js.coffee | 2 + .../controllers/simple_edit.js.coffee | 2 + .../services/order_cycle.js.coffee | 17 ++++-- .../modal_component/modal_component.scss | 13 +++++ .../admin/order_cycles_controller.rb | 28 +++++++--- app/models/order_cycle.rb | 1 + ..._date_time_warning_modal_content.html.haml | 14 +++++ .../_name_and_timing_form.html.haml | 4 +- app/views/admin/order_cycles/edit.html.haml | 50 ++++++------------ .../controllers/order_cycle_controller.js | 52 ------------------- app/webpacker/css/admin/order_cycles.scss | 24 ++------- spec/system/admin/order_cycles/edit_spec.rb | 49 +++++++---------- .../admin/subscriptions/smoke_tests_spec.rb | 5 +- 13 files changed, 108 insertions(+), 153 deletions(-) create mode 100644 app/views/admin/order_cycles/_date_time_warning_modal_content.html.haml delete mode 100644 app/webpacker/controllers/order_cycle_controller.js diff --git a/app/assets/javascripts/admin/order_cycles/controllers/edit.js.coffee b/app/assets/javascripts/admin/order_cycles/controllers/edit.js.coffee index ab5cb51249..831be6d767 100644 --- a/app/assets/javascripts/admin/order_cycles/controllers/edit.js.coffee +++ b/app/assets/javascripts/admin/order_cycles/controllers/edit.js.coffee @@ -19,6 +19,8 @@ angular.module('admin.orderCycles') $scope.submit = ($event, destination) -> $event.preventDefault() + $scope.order_cycle?.trigger_action = $($event.target).data('trigger-action'); + $scope.order_cycle?.confirm = $($event.target).data('confirm'); StatusMessage.display 'progress', t('js.saving') OrderCycle.update(destination, $scope.order_cycle_form) diff --git a/app/assets/javascripts/admin/order_cycles/controllers/simple_edit.js.coffee b/app/assets/javascripts/admin/order_cycles/controllers/simple_edit.js.coffee index fc043afaaa..625d6fb499 100644 --- a/app/assets/javascripts/admin/order_cycles/controllers/simple_edit.js.coffee +++ b/app/assets/javascripts/admin/order_cycles/controllers/simple_edit.js.coffee @@ -22,6 +22,8 @@ angular.module('admin.orderCycles').controller "AdminSimpleEditOrderCycleCtrl", $scope.submit = ($event, destination) -> $event.preventDefault() + $scope.order_cycle?.trigger_action = $($event.target).data('trigger-action'); + $scope.order_cycle?.confirm = $($event.target).data('confirm'); StatusMessage.display 'progress', t('js.saving') OrderCycle.mirrorIncomingToOutgoingProducts() OrderCycle.update(destination, $scope.order_cycle_form) if OrderCycle.confirmNoDistributors() diff --git a/app/assets/javascripts/admin/order_cycles/services/order_cycle.js.coffee b/app/assets/javascripts/admin/order_cycles/services/order_cycle.js.coffee index cb8e9e059e..7cbc5e52de 100644 --- a/app/assets/javascripts/admin/order_cycles/services/order_cycle.js.coffee +++ b/app/assets/javascripts/admin/order_cycles/services/order_cycle.js.coffee @@ -161,14 +161,25 @@ angular.module('admin.orderCycles').factory 'OrderCycle', ($resource, $window, $ StatusMessage.display('failure', t('js.order_cycles.create_failure')) update: (destination, form) -> - oc = new OrderCycleResource({order_cycle: this.dataForSubmit()}) + oc = new OrderCycleResource({ + order_cycle: this.dataForSubmit(), + confirm: this.order_cycle.confirm, + trigger_action: this.order_cycle.trigger_action + }) oc.$update {order_cycle_id: this.order_cycle.id, reloading: (if destination? then 1 else 0)}, (data) => + # Hide all confirmation buttons in warning modal + $('#linked-order-warning-modal .modal-actions button.secondary').css({ display: 'none' }) + # Show the appropriate confirmation button, open warning modal, and return + if data.trigger_action + StatusMessage.display 'notice', "You have unsaved changes" + $("#linked-order-warning-modal button[data-trigger-action=#{data.trigger_action}]").css({ display: 'block' }); + $('.warning-modal button.modal-target-trigger').trigger('click'); + return; + form.$setPristine() if form if destination? $window.location = destination else - if ($window.adminOrderCycleUpdateCallback) - adminOrderCycleUpdateCallback(data.order_cycle); StatusMessage.display 'success', t('js.order_cycles.update_success') , (response) -> if response.data.errors? diff --git a/app/components/modal_component/modal_component.scss b/app/components/modal_component/modal_component.scss index 72c0e74088..58bd7e34b9 100644 --- a/app/components/modal_component/modal_component.scss +++ b/app/components/modal_component/modal_component.scss @@ -24,6 +24,19 @@ max-width: 100%; height: auto; } + + .flex-column { + display: flex; + flex-direction: column; + } + + .gap-1 { + gap: 1rem; + } + + .gap-2 { + gap: 2rem; + } } /* prevent arrow on selected admin menu item appearing above modal */ diff --git a/app/controllers/admin/order_cycles_controller.rb b/app/controllers/admin/order_cycles_controller.rb index a52c40f7a4..ed51ed4b54 100644 --- a/app/controllers/admin/order_cycles_controller.rb +++ b/app/controllers/admin/order_cycles_controller.rb @@ -11,6 +11,7 @@ module Admin before_action :remove_protected_attrs, only: [:update] before_action :require_order_cycle_set_params, only: [:bulk_update] around_action :protect_invalid_destroy, only: :destroy + before_action :verify_datetime_change, only: :update def index respond_to do |format| @@ -70,12 +71,7 @@ module Admin respond_to do |format| flash[:success] = t('.success') if params[:reloading] == '1' format.html { redirect_to_after_update_path } - format.json { - render json: { success: true, order_cycle: { - orders_open_at: @order_cycle.orders_open_at&.strftime('%Y-%m-%d %H:%M'), - orders_close_at: @order_cycle.orders_close_at&.strftime('%Y-%m-%d %H:%M') - } } - } + format.json { render json: { success: true } } end elsif request.format.html? render :checkout_options @@ -240,7 +236,7 @@ module Admin else begin yield - rescue ActiveRecord::InvalidForeignKey + rescue ActiveRecord::InvalidForeignKey, ActiveRecord::DeleteRestrictionError redirect_to main_app.admin_order_cycles_url flash[:error] = I18n.t('admin.order_cycles.destroy_errors.orders_present') end @@ -299,5 +295,23 @@ module Admin collection_attributes: [:id] + PermittedAttributes::OrderCycle.basic_attributes ).to_h.with_indifferent_access end + + # Check that order cycle datetime values changed if it has existing orders + def verify_datetime_change + return unless params[:order_cycle][:confirm] + return unless @order_cycle.orders.exists? + return if same_dates(@order_cycle.orders_open_at&.to_s, + order_cycle_params[:orders_open_at]) && + same_dates(@order_cycle.orders_close_at&.to_s, order_cycle_params[:orders_close_at]) + + render json: { trigger_action: params[:order_cycle][:trigger_action] } + end + + def same_dates(string1, string2) + false unless string1 && string2 + + DateTime.parse(string1).strftime('%Y-%m-%d %H:%M') == + DateTime.parse(string2).strftime('%Y-%m-%d %H:%M') + end end end diff --git a/app/models/order_cycle.rb b/app/models/order_cycle.rb index 060f75e4f0..9521bcffcc 100644 --- a/app/models/order_cycle.rb +++ b/app/models/order_cycle.rb @@ -24,6 +24,7 @@ class OrderCycle < ApplicationRecord where incoming: false }, class_name: "Exchange", dependent: :destroy + has_many :orders, class_name: 'Spree::Order', dependent: :restrict_with_exception has_many :suppliers, -> { distinct }, source: :sender, through: :cached_incoming_exchanges has_many :distributors, -> { distinct }, source: :receiver, through: :cached_outgoing_exchanges has_many :order_cycle_schedules, dependent: :destroy diff --git a/app/views/admin/order_cycles/_date_time_warning_modal_content.html.haml b/app/views/admin/order_cycles/_date_time_warning_modal_content.html.haml new file mode 100644 index 0000000000..b5007460dd --- /dev/null +++ b/app/views/admin/order_cycles/_date_time_warning_modal_content.html.haml @@ -0,0 +1,14 @@ +.modal-body.flex-column-gap-1 + %h6 + = t('admin.order_cycles.edit.linked_schedule_warning_modal.title') + %div{ style: 'font-size: 1rem;' } + = t('admin.order_cycles.edit.linked_schedule_warning_modal.content') +%p.modal-actions.justify-end.gap-1 + %button.button.secondary{ "ng-click": "submit($event, null)", type: "button", style: "display: none;", data: { action: 'click->modal#close', 'trigger-action': 'save' } } + = t('admin.order_cycles.edit.linked_schedule_warning_modal.proceed') + %button.button.secondary{ "ng-click": "submit($event, '#{main_app.admin_order_cycle_incoming_path(@order_cycle)}')", type: "button", style: "display: none;", data: { action: 'click->modal#close', 'trigger-action': 'saveAndNext' } } + = t('admin.order_cycles.edit.linked_schedule_warning_modal.proceed') + %button.button.secondary{ "ng-click": "submit($event, '#{main_app.admin_order_cycles_path}')", type: "button", style: "display: none;", data: { action: 'click->modal#close', 'trigger-action': 'saveAndBack' } } + = t('admin.order_cycles.edit.linked_schedule_warning_modal.proceed') + %button.button.primary{ type: "button", 'data-action': 'click->modal#close' } + = t('admin.order_cycles.edit.linked_schedule_warning_modal.cancel') \ No newline at end of file diff --git a/app/views/admin/order_cycles/_name_and_timing_form.html.haml b/app/views/admin/order_cycles/_name_and_timing_form.html.haml index 008b5e6178..e3bc8df191 100644 --- a/app/views/admin/order_cycles/_name_and_timing_form.html.haml +++ b/app/views/admin/order_cycles/_name_and_timing_form.html.haml @@ -11,7 +11,7 @@ = f.label :orders_open_at, t('.orders_open') .omega.six.columns.fullwidth_inputs - if viewing_as_coordinator_of?(@order_cycle) - = f.text_field :orders_open_at, data: { controller: "flatpickr", "flatpickr-enable-time-value": true, action: 'order-cycle#toggleSaveBtns', 'order-cycle-target': 'input' }, 'ng-model' => 'order_cycle.orders_open_at', 'ng-if' => 'loaded()', 'change-warning' => 'order_cycle', class: "datetimepicker" + = f.text_field :orders_open_at, data: { controller: "flatpickr", "flatpickr-enable-time-value": true }, 'ng-model' => 'order_cycle.orders_open_at', 'ng-if' => 'loaded()', 'change-warning' => 'order_cycle', class: "datetimepicker" - else {{ order_cycle.orders_open_at }} @@ -24,7 +24,7 @@ = f.label :orders_close, t('.orders_close') .six.columns.omega.fullwidth_inputs - if viewing_as_coordinator_of?(@order_cycle) - = f.text_field :orders_close_at, data: { controller: "flatpickr", "flatpickr-enable-time-value": true, action: 'order-cycle#toggleSaveBtns', 'order-cycle-target': 'input' }, 'ng-model' => 'order_cycle.orders_close_at', 'ng-if' => 'loaded()', 'change-warning' => 'order_cycle', class: "datetimepicker" + = f.text_field :orders_close_at, data: { controller: "flatpickr", "flatpickr-enable-time-value": true }, 'ng-model' => 'order_cycle.orders_close_at', 'ng-if' => 'loaded()', 'change-warning' => 'order_cycle', class: "datetimepicker" - else {{ order_cycle.orders_close_at }} diff --git a/app/views/admin/order_cycles/edit.html.haml b/app/views/admin/order_cycles/edit.html.haml index 44fe2afd42..ccb5a0a13e 100644 --- a/app/views/admin/order_cycles/edit.html.haml +++ b/app/views/admin/order_cycles/edit.html.haml @@ -15,46 +15,26 @@ = t :edit_order_cycle - ng_controller = @order_cycle.simple? ? 'AdminSimpleEditOrderCycleCtrl' : 'AdminEditOrderCycleCtrl' -- has_scheduled_order = @order_cycle.schedules.exists? = admin_inject_order_cycle_instance(@order_cycle) -= form_for [main_app, :admin, @order_cycle], :url => '', :html => {:class => 'ng order_cycle', 'ng-app' => 'admin.orderCycles', 'ng-controller' => ng_controller, name: 'order_cycle_form', data: { controller: 'modal modal-link order-cycle', "modal-link-target-value": "linked-schedule-warning-modal", 'order-cycle-has-schedule-value': has_scheduled_order, 'order-cycle-init-vals-value': { 'order_cycle[orders_open_at]': @order_cycle.orders_open_at&.strftime('%Y-%m-%d %H:%M'), 'order_cycle[orders_close_at]': @order_cycle.orders_close_at&.strftime('%Y-%m-%d %H:%M') } } } do |f| += form_for [main_app, :admin, @order_cycle], :url => '', :html => {:class => 'ng order_cycle', 'ng-app' => 'admin.orderCycles', 'ng-controller' => ng_controller, name: 'order_cycle_form'} do |f| + %save-bar{ dirty: "order_cycle_form.$dirty", persist: "true" } - %div#form-actions - %input.red{ type: "button", value: t('.save'), "ng-click": "submit($event, null)", "ng-disabled": "!order_cycle_form.$dirty || order_cycle_form.$invalid" } - - if @order_cycle.simple? - %input.red{ type: "button", value: t('.save_and_back_to_list'), "ng-click": "submit($event, '#{main_app.admin_order_cycles_path}')", "ng-disabled": "!order_cycle_form.$dirty || order_cycle_form.$invalid" } - - else - %input.red{ type: "button", value: t('.save_and_next'), "ng-click": "submit($event, '#{main_app.admin_order_cycle_incoming_path(@order_cycle)}')", "ng-disabled": "!order_cycle_form.$dirty || order_cycle_form.$invalid" } - %input{ type: "button", value: t('.next'), "ng-click": "cancel('#{main_app.admin_order_cycle_incoming_path(@order_cycle)}')", "ng-disabled": "order_cycle_form.$dirty" } - %input{ type: "button", "ng-value": "order_cycle_form.$dirty ? '#{t('.cancel')}' : '#{t('.back_to_list')}'", "ng-click": "cancel('#{main_app.admin_order_cycles_path}')" } - %div#modal-actions{style: "display: none;"} - %input.red{ type: "button", value: t('.save'), "ng-disabled": "!order_cycle_form.$dirty || order_cycle_form.$invalid", data: { 'action': 'click->modal-link#open click->order-cycle#updateModalConfirmButton', 'target': 'save'} } - - if @order_cycle.simple? - %input.red{ type: "button", value: t('.save_and_back_to_list'), "ng-disabled": "!order_cycle_form.$dirty || order_cycle_form.$invalid", data: { 'action': 'click->modal-link#open click->order-cycle#updateModalConfirmButton', 'target': 'saveAndBack'} } - - else - %input.red{ type: "button", value: t('.save_and_next'), "ng-disabled": "!order_cycle_form.$dirty || order_cycle_form.$invalid", data: { 'action': 'click->modal-link#open click->order-cycle#updateModalConfirmButton', 'target': 'saveAndNext'} } - %input{ type: "button", value: t('.next'), "ng-click": "cancel('#{main_app.admin_order_cycle_incoming_path(@order_cycle)}')", "ng-disabled": "order_cycle_form.$dirty" } - %input{ type: "button", "ng-value": "order_cycle_form.$dirty ? '#{t('.cancel')}' : '#{t('.back_to_list')}'", "ng-click": "cancel('#{main_app.admin_order_cycles_path}')" } + %input.red{ type: "button", value: t('.save'), "ng-click": "submit($event, null)", "ng-disabled": "!order_cycle_form.$dirty || order_cycle_form.$invalid", data: { confirm: "true", 'trigger-action': 'save' } } + - if @order_cycle.simple? + %input.red{ type: "button", value: t('.save_and_back_to_list'), "ng-click": "submit($event, '#{main_app.admin_order_cycles_path}')", "ng-disabled": "!order_cycle_form.$dirty || order_cycle_form.$invalid", data: { confirm: "true", 'trigger-action': 'saveAndBack' } } + - else + %input.red{ type: "button", value: t('.save_and_next'), "ng-click": "submit($event, '#{main_app.admin_order_cycle_incoming_path(@order_cycle)}')", "ng-disabled": "!order_cycle_form.$dirty || order_cycle_form.$invalid", data: { confirm: "true", 'trigger-action': 'saveAndNext' } } + %input{ type: "button", value: t('.next'), "ng-click": "cancel('#{main_app.admin_order_cycle_incoming_path(@order_cycle)}')", "ng-disabled": "order_cycle_form.$dirty" } + %input{ type: "button", "ng-value": "order_cycle_form.$dirty ? '#{t('.cancel')}' : '#{t('.back_to_list')}'", "ng-click": "cancel('#{main_app.admin_order_cycles_path}')" } - if @order_cycle.simple? = render 'simple_form', f: f - else = render 'form', f: f - - if has_scheduled_order - = render ModalComponent.new(id: "linked-schedule-warning-modal", close_button: false) do - .content - .modal-body - %h6 - = t('admin.order_cycles.edit.linked_schedule_warning_modal.title') - %div{ style: 'font-size: 1rem;' } - = t('admin.order_cycles.edit.linked_schedule_warning_modal.content') - %p.modal-actions.justify-end - %button.button.secondary#modal-confirm{ type: "button", 'data-action': 'click->modal#close', style: 'display: none;', data: { 'order-cycle-target': 'modalConfirm', request: 'save' }, "ng-click": "submit($event, null)" } - = t('admin.order_cycles.edit.linked_schedule_warning_modal.proceed') - %button.button.secondary#modal-confirm{ type: "button", 'data-action': 'click->modal#close', style: 'display: none;', data: { 'order-cycle-target': 'modalConfirm', request: 'saveAndNext' }, "ng-click": "submit($event, '#{main_app.admin_order_cycle_incoming_path(@order_cycle)}')" } - = t('admin.order_cycles.edit.linked_schedule_warning_modal.proceed') - %button.button.secondary#modal-confirm{ type: "button", 'data-action': 'click->modal#close', style: 'display: none;', data: { 'order-cycle-target': 'modalConfirm', request: 'saveAndBack' }, "ng-click": "submit($event, '#{main_app.admin_order_cycles_path}')" } - = t('admin.order_cycles.edit.linked_schedule_warning_modal.proceed') - %button.button.primary{ type: "button", 'data-action': 'click->modal#close' } - = t('admin.order_cycles.edit.linked_schedule_warning_modal.cancel') \ No newline at end of file + - if @order_cycle.orders.exists? + %div.warning-modal{ data: { controller: 'modal modal-link', 'modal-link-target-value': "linked-order-warning-modal" } } + %button.modal-target-trigger{ type: 'button', data: { 'action': 'modal-link#open' }, style: 'display: none;' } + = render ModalComponent.new(id: "linked-order-warning-modal", close_button: false) do + .content.flex-column.gap-2 + = render 'date_time_warning_modal_content' \ No newline at end of file diff --git a/app/webpacker/controllers/order_cycle_controller.js b/app/webpacker/controllers/order_cycle_controller.js deleted file mode 100644 index 5bb7ecdae0..0000000000 --- a/app/webpacker/controllers/order_cycle_controller.js +++ /dev/null @@ -1,52 +0,0 @@ -import { Controller } from "stimulus"; - -export default class extends Controller { - static targets = ['input', 'modalConfirm']; - static values = { initVals: { type: Object, default: {} }, hasSchedule: { type: Boolean, default: false } }; - - connect() { - if(!this.hasScheduleValue) return; - // Attach update callback method - window.adminOrderCycleUpdateCallback = this.updateCallback.bind(this); - } - - toggleSaveBtns() { - if(!this.hasScheduleValue) return; - - // Check that datetime input value has a change - const dirty = this.inputTargets.some(ele => - new Date(this.initValsValue[`${ele.name}`]).getTime() !== new Date(ele.value).getTime()); - - // Toggle save bar action button - if (dirty) { - this.element.querySelector('#form-actions').style.display = 'none'; - this.element.querySelector('#modal-actions').style.display = 'unset'; - } else { - this.element.querySelector('#form-actions').style.display = 'unset'; - this.element.querySelector('#modal-actions').style.display = 'none'; - } - } - - updateModalConfirmButton(e) { - if(!this.hasScheduleValue) return; - // Display modal confirm button coresponding to save bar button clicked - this.modalConfirmTargets.forEach(ele => { - if (e.target.getAttribute('data-target') === ele.getAttribute('data-request')) { - ele.style.display = 'unset'; - } else { - ele.style.display = 'none'; - } - }); - } - - updateCallback(data) { - // Reset order values and update save bar buttons - this.initValsValue = { 'order_cycle[orders_open_at]': data.orders_open_at, 'order_cycle[orders_close_at]': data.orders_close_at }; - this.toggleSaveBtns(); - } - - disconnect() { - // remove attached update callback method - delete window.adminOrderCycleUpdateCallback; - } -} \ No newline at end of file diff --git a/app/webpacker/css/admin/order_cycles.scss b/app/webpacker/css/admin/order_cycles.scss index cad5d3afb6..ace5c65498 100644 --- a/app/webpacker/css/admin/order_cycles.scss +++ b/app/webpacker/css/admin/order_cycles.scss @@ -63,24 +63,6 @@ form.order_cycle { } } -#linked-schedule-warning-modal { - .reveal-modal { - width: 28rem; - - .content { - display: flex; - flex-direction: column; - gap: 2rem; - - .modal-body { - display: flex; - flex-direction: column; - gap: 1rem; - } - - .modal-actions { - gap: 1rem; - } - } - } -} +#linked-order-warning-modal .reveal-modal{ + width: 28rem; +} \ No newline at end of file diff --git a/spec/system/admin/order_cycles/edit_spec.rb b/spec/system/admin/order_cycles/edit_spec.rb index ff157e5539..b3824bcdf9 100644 --- a/spec/system/admin/order_cycles/edit_spec.rb +++ b/spec/system/admin/order_cycles/edit_spec.rb @@ -10,29 +10,19 @@ RSpec.describe ' include AuthenticationHelper include WebHelper - let(:oc0) { - create(:simple_order_cycle, name: 'oc0', - orders_open_at: nil, orders_close_at: nil) - } - let(:oc1) { create(:order_cycle, name: 'oc1') } - - context 'when cycle has attached schedule(s)' do - it "properly toggles order cycle save bar buttons to show warning modal" do - create(:schedule, name: 'Schedule1', order_cycles: [oc0]) + context 'when cycle has attached order(s)' do + let(:order) { create(:order_without_full_payment) } + it "show warning modal when datetime field values change" do # When I go to the admin order cycle edit page login_as_admin - visit edit_admin_order_cycle_path(oc0) - - expect(page).to have_selector("#linked-schedule-warning-modal") - expect(page).not_to have_selector("#modal-actions") - expect(page).to have_selector("#form-actions") + visit edit_admin_order_cycle_path(order.order_cycle) # change non-date range field - fill_in 'order_cycle_name', with: "OC0 name updated" + fill_in 'order_cycle_name', with: "Order cycle name updated" expect(page).to have_content('You have unsaved changes') click_button('Save') - expect(page).not_to have_selector('#linked-schedule-warning-modal .reveal-modal.in') + expect(page).not_to have_content "Orders are linked to this order cycle" expect(page).to have_content('Your order cycle has been updated.') # change date range field value @@ -40,33 +30,32 @@ RSpec.describe ' find('#order_cycle_orders_close_at').click select_datetime_from_datepicker Time.zone.at(time) - # Enable savebar save buttons to open warning modal expect(page.find('#order_cycle_orders_close_at').value).to eq time.strftime('%Y-%m-%d %H:%M') - expect(page).not_to have_selector("#form-actions") - expect(page).to have_selector("#modal-actions") expect(page).to have_content('You have unsaved changes') - expect(page).not_to have_selector('#linked-schedule-warning-modal .reveal-modal.in') # click save to open warning modal click_button('Save') - expect(page).to have_selector('#linked-schedule-warning-modal .reveal-modal.in') + expect(page).to have_content('You have unsaved changes') + expect(page).to have_content "Orders are linked to this order cycle." # confirm to close modal and update order cycle changed fields click_button('Proceed anyway') - expect(page).not_to have_selector('#linked-schedule-warning-modal .reveal-modal.in') + expect(page).not_to have_content "Orders are linked to this cycle" + expect(page).to have_content('Your order cycle has been updated.') expect(page.find('#order_cycle_orders_close_at').value).to eq time.strftime('%Y-%m-%d %H:%M') end end context 'when cycle does not have attached schedule' do + let(:order_cycle) { + create(:simple_order_cycle, name: 'My Order cycle', + orders_open_at: nil, orders_close_at: nil) + } + it "does not render warning modal" do # When I go to the admin order cycle edit page login_as_admin - visit edit_admin_order_cycle_path(oc1) - - expect(page).not_to have_selector("#linked-schedule-warning-modal") - expect(page).not_to have_selector("#modal-actions") - expect(page).to have_selector("#form-actions") + visit edit_admin_order_cycle_path(order_cycle) # change non-date range field value fill_in 'order_cycle_name', with: "OC1 name updated" @@ -74,17 +63,17 @@ RSpec.describe ' # click save click_button('Save') - expect(page).not_to have_selector('#linked-schedule-warning-modal .reveal-modal.in') + expect(page.find('#order_cycle_name').value).to eq 'OC1 name updated' expect(page).to have_content('Your order cycle has been updated.') - # change date range field value + # Now change date range field value time = DateTime.current find('#order_cycle_orders_close_at').click select_datetime_from_datepicker Time.zone.at(time) expect(page).to have_content('You have unsaved changes') click_button('Save') - expect(page).not_to have_selector("#modal-actions") + expect(page.find('#order_cycle_orders_close_at').value).to eq time.strftime('%Y-%m-%d %H:%M') expect(page).to have_content('Your order cycle has been updated.') end end diff --git a/spec/system/admin/subscriptions/smoke_tests_spec.rb b/spec/system/admin/subscriptions/smoke_tests_spec.rb index 837555b9ff..19f224f41d 100644 --- a/spec/system/admin/subscriptions/smoke_tests_spec.rb +++ b/spec/system/admin/subscriptions/smoke_tests_spec.rb @@ -125,9 +125,8 @@ RSpec.describe 'Subscriptions' do select_datetime_from_datepicker Time.zone.at(1.month.from_now) find("body").send_keys(:escape) - # Click save and comfirm in warning modal (because date time range value was changed) - click_button('Save') - click_button('Proceed anyway') + click_button 'Save' + visit edit_admin_subscription_path(subscription) click_button 'edit-products' From 6a438a07feb1dd676dec3335198bffca973c9d0e Mon Sep 17 00:00:00 2001 From: wandji20 Date: Thu, 1 Aug 2024 09:09:26 +0100 Subject: [PATCH 024/206] Add stimulus controler to monitor order cycle status message data attribute change and trigger warning modal [OFN-11613] --- .../services/order_cycle.js.coffee | 11 ++---- .../utils/services/status_message.js.coffee | 10 ++++-- .../templates/admin/save_bar.html.haml | 2 +- .../admin/order_cycles_controller.rb | 16 ++++----- ..._date_time_warning_modal_content.html.haml | 14 ++++---- app/views/admin/order_cycles/edit.html.haml | 26 +++++++------- .../order_cycle_form_controller.js | 36 +++++++++++++++++++ config/locales/en.yml | 11 +++--- 8 files changed, 81 insertions(+), 45 deletions(-) create mode 100644 app/webpacker/controllers/order_cycle_form_controller.js diff --git a/app/assets/javascripts/admin/order_cycles/services/order_cycle.js.coffee b/app/assets/javascripts/admin/order_cycles/services/order_cycle.js.coffee index 7cbc5e52de..58dc0a8264 100644 --- a/app/assets/javascripts/admin/order_cycles/services/order_cycle.js.coffee +++ b/app/assets/javascripts/admin/order_cycles/services/order_cycle.js.coffee @@ -167,15 +167,6 @@ angular.module('admin.orderCycles').factory 'OrderCycle', ($resource, $window, $ trigger_action: this.order_cycle.trigger_action }) oc.$update {order_cycle_id: this.order_cycle.id, reloading: (if destination? then 1 else 0)}, (data) => - # Hide all confirmation buttons in warning modal - $('#linked-order-warning-modal .modal-actions button.secondary').css({ display: 'none' }) - # Show the appropriate confirmation button, open warning modal, and return - if data.trigger_action - StatusMessage.display 'notice', "You have unsaved changes" - $("#linked-order-warning-modal button[data-trigger-action=#{data.trigger_action}]").css({ display: 'block' }); - $('.warning-modal button.modal-target-trigger').trigger('click'); - return; - form.$setPristine() if form if destination? $window.location = destination @@ -184,6 +175,8 @@ angular.module('admin.orderCycles').factory 'OrderCycle', ($resource, $window, $ , (response) -> if response.data.errors? StatusMessage.display('failure', response.data.errors[0]) + else if (response.data.trigger_action) + StatusMessage.display('notice', t('js.order_cycles.unsaved_changes'), response.data.trigger_action) else StatusMessage.display('failure', t('js.order_cycles.update_failure')) diff --git a/app/assets/javascripts/admin/utils/services/status_message.js.coffee b/app/assets/javascripts/admin/utils/services/status_message.js.coffee index 432c6ecf70..8355209fc1 100644 --- a/app/assets/javascripts/admin/utils/services/status_message.js.coffee +++ b/app/assets/javascripts/admin/utils/services/status_message.js.coffee @@ -10,7 +10,9 @@ angular.module("admin.utils").factory "StatusMessage", -> statusMessage: text: "" - style: {} + style: {}, + type: null, + actionName: null invalidMessage: "" @@ -23,11 +25,15 @@ angular.module("admin.utils").factory "StatusMessage", -> active: -> @statusMessage.text != '' - display: (type, text) -> + display: (type, text, actionName = null) -> @statusMessage.text = text + @statusMessage.type = type + @statusMessage.actionName = actionName @statusMessage.style = @types[type].style null clear: -> @statusMessage.text = '' @statusMessage.style = {} + @statusMessage.type = null + @statusMessage.actionName = null diff --git a/app/assets/javascripts/templates/admin/save_bar.html.haml b/app/assets/javascripts/templates/admin/save_bar.html.haml index 2a33901459..aed3708f8b 100644 --- a/app/assets/javascripts/templates/admin/save_bar.html.haml +++ b/app/assets/javascripts/templates/admin/save_bar.html.haml @@ -1,7 +1,7 @@ #save-bar.animate-show{ "ng-show": 'dirty || persist || StatusMessage.active()' } .container .seven.columns.alpha - %h5#status-message{ "ng-show": "StatusMessage.invalidMessage == ''", "ng-style": 'StatusMessage.statusMessage.style' } + %h5#status-message{ "ng-show": "StatusMessage.invalidMessage == ''", "ng-style": 'StatusMessage.statusMessage.style', data: { 'order-cycle-form-target': 'element' }, "ng-attr-data-type": "{{StatusMessage.statusMessage.type}}", "ng-attr-data-action-name": "{{StatusMessage.statusMessage.actionName}}" } {{ StatusMessage.statusMessage.text || " " }} %h5#status-message{ style: 'color: #C85136', "ng-show": "StatusMessage.invalidMessage !== ''" } {{ StatusMessage.invalidMessage || " " }} diff --git a/app/controllers/admin/order_cycles_controller.rb b/app/controllers/admin/order_cycles_controller.rb index ed51ed4b54..f87c46f8b5 100644 --- a/app/controllers/admin/order_cycles_controller.rb +++ b/app/controllers/admin/order_cycles_controller.rb @@ -300,18 +300,18 @@ module Admin def verify_datetime_change return unless params[:order_cycle][:confirm] return unless @order_cycle.orders.exists? - return if same_dates(@order_cycle.orders_open_at&.to_s, - order_cycle_params[:orders_open_at]) && - same_dates(@order_cycle.orders_close_at&.to_s, order_cycle_params[:orders_close_at]) + return if same_dates(@order_cycle.orders_open_at, order_cycle_params[:orders_open_at]) && + same_dates(@order_cycle.orders_close_at, order_cycle_params[:orders_close_at]) - render json: { trigger_action: params[:order_cycle][:trigger_action] } + render json: { trigger_action: params[:order_cycle][:trigger_action] }, + status: :unprocessable_entity end - def same_dates(string1, string2) - false unless string1 && string2 + def same_dates(date, string) + false unless date && string - DateTime.parse(string1).strftime('%Y-%m-%d %H:%M') == - DateTime.parse(string2).strftime('%Y-%m-%d %H:%M') + DateTime.parse(string).strftime('%Y-%m-%d %H:%M') == + date.to_datetime.strftime('%Y-%m-%d %H:%M') end end end diff --git a/app/views/admin/order_cycles/_date_time_warning_modal_content.html.haml b/app/views/admin/order_cycles/_date_time_warning_modal_content.html.haml index b5007460dd..c70b964c13 100644 --- a/app/views/admin/order_cycles/_date_time_warning_modal_content.html.haml +++ b/app/views/admin/order_cycles/_date_time_warning_modal_content.html.haml @@ -1,14 +1,14 @@ -.modal-body.flex-column-gap-1 +.flex-column-gap-1 %h6 - = t('admin.order_cycles.edit.linked_schedule_warning_modal.title') + = t('.title') %div{ style: 'font-size: 1rem;' } - = t('admin.order_cycles.edit.linked_schedule_warning_modal.content') + = t('.content') %p.modal-actions.justify-end.gap-1 %button.button.secondary{ "ng-click": "submit($event, null)", type: "button", style: "display: none;", data: { action: 'click->modal#close', 'trigger-action': 'save' } } - = t('admin.order_cycles.edit.linked_schedule_warning_modal.proceed') + = t('.proceed') %button.button.secondary{ "ng-click": "submit($event, '#{main_app.admin_order_cycle_incoming_path(@order_cycle)}')", type: "button", style: "display: none;", data: { action: 'click->modal#close', 'trigger-action': 'saveAndNext' } } - = t('admin.order_cycles.edit.linked_schedule_warning_modal.proceed') + = t('.proceed') %button.button.secondary{ "ng-click": "submit($event, '#{main_app.admin_order_cycles_path}')", type: "button", style: "display: none;", data: { action: 'click->modal#close', 'trigger-action': 'saveAndBack' } } - = t('admin.order_cycles.edit.linked_schedule_warning_modal.proceed') + = t('.proceed') %button.button.primary{ type: "button", 'data-action': 'click->modal#close' } - = t('admin.order_cycles.edit.linked_schedule_warning_modal.cancel') \ No newline at end of file + = t('.cancel') \ No newline at end of file diff --git a/app/views/admin/order_cycles/edit.html.haml b/app/views/admin/order_cycles/edit.html.haml index ccb5a0a13e..4b724a3593 100644 --- a/app/views/admin/order_cycles/edit.html.haml +++ b/app/views/admin/order_cycles/edit.html.haml @@ -16,23 +16,23 @@ - ng_controller = @order_cycle.simple? ? 'AdminSimpleEditOrderCycleCtrl' : 'AdminEditOrderCycleCtrl' = admin_inject_order_cycle_instance(@order_cycle) -= form_for [main_app, :admin, @order_cycle], :url => '', :html => {:class => 'ng order_cycle', 'ng-app' => 'admin.orderCycles', 'ng-controller' => ng_controller, name: 'order_cycle_form'} do |f| +%div{ data: { controller: 'order-cycle-form' } } + = form_for [main_app, :admin, @order_cycle], :url => '', :html => {:class => 'ng order_cycle', 'ng-app' => 'admin.orderCycles', 'ng-controller' => ng_controller, name: 'order_cycle_form'} do |f| + + %save-bar{ dirty: "order_cycle_form.$dirty", persist: "true" } + %input.red{ type: "button", value: t('.save'), "ng-click": "submit($event, null)", "ng-disabled": "!order_cycle_form.$dirty || order_cycle_form.$invalid", data: { confirm: "true", 'trigger-action': 'save' } } + - if @order_cycle.simple? + %input.red{ type: "button", value: t('.save_and_back_to_list'), "ng-click": "submit($event, '#{main_app.admin_order_cycles_path}')", "ng-disabled": "!order_cycle_form.$dirty || order_cycle_form.$invalid", data: { confirm: "true", 'trigger-action': 'saveAndBack' } } + - else + %input.red{ type: "button", value: t('.save_and_next'), "ng-click": "submit($event, '#{main_app.admin_order_cycle_incoming_path(@order_cycle)}')", "ng-disabled": "!order_cycle_form.$dirty || order_cycle_form.$invalid", data: { confirm: "true", 'trigger-action': 'saveAndNext' } } + %input{ type: "button", value: t('.next'), "ng-click": "cancel('#{main_app.admin_order_cycle_incoming_path(@order_cycle)}')", "ng-disabled": "order_cycle_form.$dirty" } + %input{ type: "button", "ng-value": "order_cycle_form.$dirty ? '#{t('.cancel')}' : '#{t('.back_to_list')}'", "ng-click": "cancel('#{main_app.admin_order_cycles_path}')" } - %save-bar{ dirty: "order_cycle_form.$dirty", persist: "true" } - %input.red{ type: "button", value: t('.save'), "ng-click": "submit($event, null)", "ng-disabled": "!order_cycle_form.$dirty || order_cycle_form.$invalid", data: { confirm: "true", 'trigger-action': 'save' } } - if @order_cycle.simple? - %input.red{ type: "button", value: t('.save_and_back_to_list'), "ng-click": "submit($event, '#{main_app.admin_order_cycles_path}')", "ng-disabled": "!order_cycle_form.$dirty || order_cycle_form.$invalid", data: { confirm: "true", 'trigger-action': 'saveAndBack' } } + = render 'simple_form', f: f - else - %input.red{ type: "button", value: t('.save_and_next'), "ng-click": "submit($event, '#{main_app.admin_order_cycle_incoming_path(@order_cycle)}')", "ng-disabled": "!order_cycle_form.$dirty || order_cycle_form.$invalid", data: { confirm: "true", 'trigger-action': 'saveAndNext' } } - %input{ type: "button", value: t('.next'), "ng-click": "cancel('#{main_app.admin_order_cycle_incoming_path(@order_cycle)}')", "ng-disabled": "order_cycle_form.$dirty" } - %input{ type: "button", "ng-value": "order_cycle_form.$dirty ? '#{t('.cancel')}' : '#{t('.back_to_list')}'", "ng-click": "cancel('#{main_app.admin_order_cycles_path}')" } + = render 'form', f: f - - if @order_cycle.simple? - = render 'simple_form', f: f - - else - = render 'form', f: f - - - if @order_cycle.orders.exists? %div.warning-modal{ data: { controller: 'modal modal-link', 'modal-link-target-value': "linked-order-warning-modal" } } %button.modal-target-trigger{ type: 'button', data: { 'action': 'modal-link#open' }, style: 'display: none;' } = render ModalComponent.new(id: "linked-order-warning-modal", close_button: false) do diff --git a/app/webpacker/controllers/order_cycle_form_controller.js b/app/webpacker/controllers/order_cycle_form_controller.js new file mode 100644 index 0000000000..03128c09d0 --- /dev/null +++ b/app/webpacker/controllers/order_cycle_form_controller.js @@ -0,0 +1,36 @@ +import { Controller } from "stimulus"; + +export default class extends Controller { + static targets = ['element'] + connect() { + this.observer = new MutationObserver(this.updateCallback); + this.observer.observe( + this.elementTarget, + { attributes: true, attributeOldValue: true, attributeFilter: ['data-type'] } + ); + } + + // Callback to trigger warning modal + updateCallback(mutationsList) { + const newDataType = $('#status-message').attr('data-type'); + const actionName = $('#status-message').attr('data-action-name'); + if(!actionName) return; + + for(let mutation of mutationsList) { + if (mutation.type === 'attributes' && mutation.attributeName === 'data-type') { + // Only trigger warning modal when notice display (notice) is preceeded by progress display (progress) + if(mutation.oldValue === 'progress' && newDataType === 'notice') { + // Hide all confirmation buttons in warning modal + $('#linked-order-warning-modal .modal-actions button.secondary').css({ display: 'none' }) + // Show the appropriate confirmation button, open warning modal, and return + $(`#linked-order-warning-modal button[data-trigger-action=${actionName}]`).css({ display: 'block' }); + $('.warning-modal button.modal-target-trigger').trigger('click'); + } + } + } + } + + disconnect() { + this.observer.disconnect(); + } +} diff --git a/config/locales/en.yml b/config/locales/en.yml index 8e1e1ad995..c3f4ba220e 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1506,11 +1506,11 @@ en: choose_products_from: "Choose Products From:" re_notify_producers: Re notify producers notify_producers_tip: This will send an email to each producer with the list of their orders. - linked_schedule_warning_modal: - title: 'Orders are linked to this order cycle.' - content: 'If you wish to create a new order cycle, it is recommended to duplicate the order cycle first and then change the dates.' - proceed: 'Proceed anyway' - cancel: 'Cancel' + date_time_warning_modal_content: + title: 'Orders are linked to this order cycle.' + content: 'If you wish to create a new order cycle, it is recommended to duplicate the order cycle first and then change the dates.' + proceed: 'Proceed anyway' + cancel: 'Cancel' incoming: incoming: "Incoming" supplier: "Supplier" @@ -3703,6 +3703,7 @@ See the %{link} to find out more about %{sitename}'s features and to start using This will set stock level to zero on all products for this enterprise that are not present in the uploaded file. order_cycles: + unsaved_changes: "You have unsaved changes" create_failure: "Failed to create order cycle" update_success: 'Your order cycle has been updated.' update_failure: "Failed to update order cycle" From ad3e772944063b81f7edaa266dae26675f13a1a6 Mon Sep 17 00:00:00 2001 From: wandji20 Date: Tue, 6 Aug 2024 00:42:33 +0100 Subject: [PATCH 025/206] Refactor and update order cycle form controller [OFN-11613] --- .../templates/admin/save_bar.html.haml | 2 +- .../admin/order_cycles_controller.rb | 3 +-- .../order_cycle_form_controller.js | 23 +++++++++++++------ 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/app/assets/javascripts/templates/admin/save_bar.html.haml b/app/assets/javascripts/templates/admin/save_bar.html.haml index aed3708f8b..e0ab0f437e 100644 --- a/app/assets/javascripts/templates/admin/save_bar.html.haml +++ b/app/assets/javascripts/templates/admin/save_bar.html.haml @@ -1,7 +1,7 @@ #save-bar.animate-show{ "ng-show": 'dirty || persist || StatusMessage.active()' } .container .seven.columns.alpha - %h5#status-message{ "ng-show": "StatusMessage.invalidMessage == ''", "ng-style": 'StatusMessage.statusMessage.style', data: { 'order-cycle-form-target': 'element' }, "ng-attr-data-type": "{{StatusMessage.statusMessage.type}}", "ng-attr-data-action-name": "{{StatusMessage.statusMessage.actionName}}" } + %h5#status-message{ "ng-show": "StatusMessage.invalidMessage == ''", "ng-style": 'StatusMessage.statusMessage.style', data: { 'order-cycle-form-target': 'statusMessage' }, "ng-attr-data-type": "{{StatusMessage.statusMessage.type}}", "ng-attr-data-action-name": "{{StatusMessage.statusMessage.actionName}}" } {{ StatusMessage.statusMessage.text || " " }} %h5#status-message{ style: 'color: #C85136', "ng-show": "StatusMessage.invalidMessage !== ''" } {{ StatusMessage.invalidMessage || " " }} diff --git a/app/controllers/admin/order_cycles_controller.rb b/app/controllers/admin/order_cycles_controller.rb index f87c46f8b5..a94fa8a9b7 100644 --- a/app/controllers/admin/order_cycles_controller.rb +++ b/app/controllers/admin/order_cycles_controller.rb @@ -310,8 +310,7 @@ module Admin def same_dates(date, string) false unless date && string - DateTime.parse(string).strftime('%Y-%m-%d %H:%M') == - date.to_datetime.strftime('%Y-%m-%d %H:%M') + DateTime.parse(string).to_fs(:short) == date.to_fs(:short) end end end diff --git a/app/webpacker/controllers/order_cycle_form_controller.js b/app/webpacker/controllers/order_cycle_form_controller.js index 03128c09d0..4215e8060c 100644 --- a/app/webpacker/controllers/order_cycle_form_controller.js +++ b/app/webpacker/controllers/order_cycle_form_controller.js @@ -1,19 +1,20 @@ import { Controller } from "stimulus"; export default class extends Controller { - static targets = ['element'] + static targets = ['statusMessage'] connect() { + console.log(this.statusMessageTarget) this.observer = new MutationObserver(this.updateCallback); this.observer.observe( - this.elementTarget, + this.statusMessageTarget, { attributes: true, attributeOldValue: true, attributeFilter: ['data-type'] } ); } // Callback to trigger warning modal updateCallback(mutationsList) { - const newDataType = $('#status-message').attr('data-type'); - const actionName = $('#status-message').attr('data-action-name'); + const newDataType = document.getElementById('status-message').getAttribute('data-type'); + const actionName = document.getElementById('status-message').getAttribute('data-action-name'); if(!actionName) return; for(let mutation of mutationsList) { @@ -21,10 +22,18 @@ export default class extends Controller { // Only trigger warning modal when notice display (notice) is preceeded by progress display (progress) if(mutation.oldValue === 'progress' && newDataType === 'notice') { // Hide all confirmation buttons in warning modal - $('#linked-order-warning-modal .modal-actions button.secondary').css({ display: 'none' }) + document.querySelectorAll( + '#linked-order-warning-modal .modal-actions button.secondary' + ).forEach((node) => { + node.style.display = 'none'; + }); // Show the appropriate confirmation button, open warning modal, and return - $(`#linked-order-warning-modal button[data-trigger-action=${actionName}]`).css({ display: 'block' }); - $('.warning-modal button.modal-target-trigger').trigger('click'); + document.querySelectorAll( + `#linked-order-warning-modal button[data-trigger-action=${actionName}]` + ).forEach((node) => { + node.style.display = 'block'; + }) + document.querySelector('.warning-modal button.modal-target-trigger').click(); } } } From 43d983cac276c30acfc35224442dc0d804945678 Mon Sep 17 00:00:00 2001 From: Gaetan Craig-Riou Date: Wed, 7 Aug 2024 09:34:35 +1000 Subject: [PATCH 026/206] Remoce left over console.log --- app/webpacker/controllers/order_cycle_form_controller.js | 1 - 1 file changed, 1 deletion(-) diff --git a/app/webpacker/controllers/order_cycle_form_controller.js b/app/webpacker/controllers/order_cycle_form_controller.js index 4215e8060c..f4c8809360 100644 --- a/app/webpacker/controllers/order_cycle_form_controller.js +++ b/app/webpacker/controllers/order_cycle_form_controller.js @@ -3,7 +3,6 @@ import { Controller } from "stimulus"; export default class extends Controller { static targets = ['statusMessage'] connect() { - console.log(this.statusMessageTarget) this.observer = new MutationObserver(this.updateCallback); this.observer.observe( this.statusMessageTarget, From 83bf19084b3959e4e276b6617d5c09553f68d597 Mon Sep 17 00:00:00 2001 From: EdwardLi-coder <2023edwardll@gmail.com> Date: Mon, 12 Aug 2024 16:29:52 +0800 Subject: [PATCH 027/206] remove fail test --- spec/system/admin/products_v3/update_spec.rb | 6 ------ 1 file changed, 6 deletions(-) diff --git a/spec/system/admin/products_v3/update_spec.rb b/spec/system/admin/products_v3/update_spec.rb index 8537103d09..fec4236177 100644 --- a/spec/system/admin/products_v3/update_spec.rb +++ b/spec/system/admin/products_v3/update_spec.rb @@ -682,12 +682,6 @@ RSpec.describe 'As an enterprise user, I can update my products' do end end - it "fails intentionally to generate screenshot" do - within ".reveal-modal" do - expect(page).to have_content "This text does not exist" - end - end - it 'shows a modal telling not a valid image when uploading a non valid image file' do within ".reveal-modal" do attach_file 'image[attachment]', From ab2968ffd2460add0ed7c1ae5d7b1a3f5684a94f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Aug 2024 09:25:57 +0000 Subject: [PATCH 028/206] Bump mrujs from 1.0.0 to 1.0.1 Bumps [mrujs](https://github.com/KonnorRogers/mrujs) from 1.0.0 to 1.0.1. - [Changelog](https://github.com/KonnorRogers/mrujs/blob/main/CHANGELOG.md) - [Commits](https://github.com/KonnorRogers/mrujs/compare/v1.0.0...v1.0.1) --- updated-dependencies: - dependency-name: mrujs dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 1c20cce038..ed5a1f295c 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "leaflet-geosearch": "4.0.0", "leaflet-providers": "2.0.0", "moment": "^2.30.1", - "mrujs": "^1.0.0", + "mrujs": "^1.0.1", "select2": "^4.0.13", "shortcut-buttons-flatpickr": "^0.4.0", "stimulus": "^3.2.2", diff --git a/yarn.lock b/yarn.lock index 1b81a7e1ff..05afddbab5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6141,10 +6141,10 @@ mri@^1.2.0: resolved "https://registry.yarnpkg.com/mri/-/mri-1.2.0.tgz#6721480fec2a11a4889861115a48b6cbe7cc8f0b" integrity sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA== -mrujs@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/mrujs/-/mrujs-1.0.0.tgz#b1d5bf86dd17612c2758388005919fb44a9878f6" - integrity sha512-uQv7o4fyrO+qDKJhBh7B9ox3tA1gVrdksSueZPEkyQ++re70G6D27HJpzKm6JlsnRE98Q5F4BdtJFYUv1j2OnQ== +mrujs@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/mrujs/-/mrujs-1.0.1.tgz#e015e219bec52bff67eb4948cdf281dc12eb36db" + integrity sha512-spkPPqlVaZ8EW57Ggg5EBuki65LjH7B5WogqxI6M2DpsKfXlrFHnqT7AbTtWTnKyhQ5HZEBm1DjmgYIjRYX0RA== dependencies: morphdom ">=2.6.0 <3.0.0" From 6bd0f2c08818c8c25f8e5707925ce5763765cbdd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Aug 2024 09:26:19 +0000 Subject: [PATCH 029/206] Bump js-big-decimal from 2.0.7 to 2.1.0 Bumps [js-big-decimal](https://github.com/royNiladri/js-big-decimal) from 2.0.7 to 2.1.0. - [Release notes](https://github.com/royNiladri/js-big-decimal/releases) - [Commits](https://github.com/royNiladri/js-big-decimal/compare/v2.0.7...v2.1.0) --- updated-dependencies: - dependency-name: js-big-decimal dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 1c20cce038..dbe98bb99c 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "foundation-sites": "^5.5.3", "hotkeys-js": "^3.13.7", "jquery-ui": "1.14.0", - "js-big-decimal": "^2.0.7", + "js-big-decimal": "^2.1.0", "leaflet": "1.9.4", "leaflet-geosearch": "4.0.0", "leaflet-providers": "2.0.0", diff --git a/yarn.lock b/yarn.lock index 1b81a7e1ff..815a0a78c4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5502,10 +5502,10 @@ jquery-ui@1.14.0: resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.7.1.tgz#083ef98927c9a6a74d05a6af02806566d16274de" integrity sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg== -js-big-decimal@^2.0.7: - version "2.0.7" - resolved "https://registry.yarnpkg.com/js-big-decimal/-/js-big-decimal-2.0.7.tgz#fb9b44b4c1eae08903cb191c0cf37b82f3a8d7c4" - integrity sha512-XGc79t2Iv3b7LFlYaTT8WoQBuWL4K81aST+dq2YGHV6giedbnoG0s33ju24Uw/BGqLYfPPgn4HGRrPS2mfKk3Q== +js-big-decimal@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/js-big-decimal/-/js-big-decimal-2.1.0.tgz#9e06c10590969aa1ffb8570af25072391b6973bb" + integrity sha512-6BOJi5gJ/u5KIOiRjwDnYee64wDw48/mzObV10L5ZYZ/u3ujpzJrO1JyIdIYbfxJPhIT7+oKn7d/1Fw1kOsh2w== js-tokens@^4.0.0: version "4.0.0" From e2e3aa9281a475ac2ef00e1316eb659adf1ccd03 Mon Sep 17 00:00:00 2001 From: Ahmed Ejaz Date: Mon, 12 Aug 2024 15:16:47 +0500 Subject: [PATCH 030/206] 12698: add specs --- spec/system/admin/products_spec.rb | 92 ++++++++++++------------------ 1 file changed, 37 insertions(+), 55 deletions(-) diff --git a/spec/system/admin/products_spec.rb b/spec/system/admin/products_spec.rb index abe75464db..12c064dda4 100644 --- a/spec/system/admin/products_spec.rb +++ b/spec/system/admin/products_spec.rb @@ -299,6 +299,43 @@ RSpec.describe ' describe "editing page" do let!(:product) { create(:simple_product, name: 'a product', supplier_id: supplier2.id) } + describe "'Back to products list' and 'Cancel' buttons" do + context "navigates to edit from the bulk product update page with searched results" do + it "should navigate back to the same searched results page" do + # Navigating to a searched URL + visit admin_products_url({ + page: 1, + per_page: 25, + search_term: 'product', + producer_id: supplier2.id + }) + + products_page_url = current_url + within row_containing_name('a product') do + page.find(".vertical-ellipsis-menu").click + click_link('Edit', href: spree.edit_admin_product_path(product)) + end + + expect(page).to have_link('Back to products list', + href: products_page_url) + expect(page).to have_link('Cancel', + href: products_page_url) + end + end + + context "directly navigates to the edit page" do + it "should navigate back to all the products page" do + # Navigating to a searched URL + visit spree.edit_admin_product_path(product) + + expect(page).to have_link('Back to products list', + href: admin_products_url) + expect(page).to have_link('Cancel', + href: admin_products_url) + end + end + end + it "editing a product" do visit spree.edit_admin_product_path product @@ -309,61 +346,6 @@ RSpec.describe ' expect(product.description).to eq("
A description...
") end - it "editing a product comming from the bulk product update page with filter" do - visit spree.edit_admin_product_path(product, filter) - - click_button 'Update' - expect(flash_message).to eq('Product "a product" has been successfully updated!') - - # Check the url still includes the filters - uri = URI.parse(current_url) - expect("#{uri.path}?#{uri.query}").to eq spree.edit_admin_product_path(product, filter) - - # Link back to the bulk product update page should include the filters - expected_admin_product_url = - Regexp.new(Regexp.escape("#{spree.admin_products_path}#?#{filter.to_query}")) - expect(page).to have_link('Back to products list', - href: expected_admin_product_url) - expect(page).to have_link('Cancel', href: expected_admin_product_url) - - expected_product_url = Regexp.new(Regexp.escape(spree.edit_admin_product_path( - product.id, filter - ))) - expect(page).to have_link('Product Details', - href: expected_product_url) - - expected_product_image_url = Regexp.new(Regexp.escape(spree.admin_product_images_path( - product.id, filter - ))) - expect(page).to have_link('Images', - href: expected_product_image_url) - - expected_product_variant_url = Regexp.new(Regexp.escape(spree.admin_product_variants_path( - product.id, filter - ))) - expect(page).to have_link('Variants', - href: expected_product_variant_url) - - expected_product_properties_url = - Regexp.new(Regexp.escape(spree.admin_product_product_properties_path( - product.id, filter - ))) - expect(page).to have_link('Product Properties', - href: expected_product_properties_url) - - expected_product_group_buy_option_url = - Regexp.new(Regexp.escape(spree.group_buy_options_admin_product_path( - product.id, filter - ))) - expect(page).to have_link('Group Buy Options', - href: expected_product_group_buy_option_url) - - expected_product_seo_url = Regexp.new(Regexp.escape(spree.seo_admin_product_path( - product.id, filter - ))) - expect(page).to have_link('Search', href: expected_product_seo_url) - end - it "editing product group buy options" do visit spree.edit_admin_product_path product within('#sidebar') { click_link 'Group Buy Options' } From a85cfab506adf3c95e97c13d87ff97886df687a0 Mon Sep 17 00:00:00 2001 From: wandji20 Date: Wed, 7 Aug 2024 19:35:24 +0100 Subject: [PATCH 031/206] Remove awesome nested set gem and dependencies [OFN-11636] --- Gemfile | 1 - .../spree/taxons/taxon_tree_menu.js.coffee | 21 --- .../admin/spree/taxons/taxonomy.js.coffee | 139 ------------------ .../api/v0/taxonomies_controller.rb | 16 -- app/controllers/api/v0/taxons_controller.rb | 30 +--- .../spree/admin/taxonomies_controller.rb | 27 ---- .../spree/admin/taxons_controller.rb | 136 ++++++----------- app/helpers/spree/admin/taxons_helper.rb | 11 -- app/models/spree/ability.rb | 1 - app/models/spree/taxon.rb | 19 +-- app/models/spree/taxonomy.rb | 27 ---- app/serializers/api/admin/taxon_serializer.rb | 2 +- app/serializers/api/taxon_serializer.rb | 2 +- .../shared/_configuration_menu.html.haml | 2 +- app/views/spree/admin/shared/_tabs.html.haml | 2 +- .../spree/admin/taxonomies/_form.html.haml | 7 - .../spree/admin/taxonomies/_js_head.html.erb | 13 -- .../spree/admin/taxonomies/_list.html.haml | 19 --- .../spree/admin/taxonomies/_taxon.html.haml | 7 - app/views/spree/admin/taxonomies/edit.haml | 27 ---- .../spree/admin/taxonomies/index.html.haml | 11 -- .../spree/admin/taxonomies/new.html.haml | 15 -- app/views/spree/admin/taxons/_form.html.haml | 3 +- .../taxons/destroy_taxon.turbo_stream.haml | 4 + app/views/spree/admin/taxons/edit.html.haml | 12 +- app/views/spree/admin/taxons/index.html.haml | 27 ++++ app/views/spree/admin/taxons/new.html.haml | 14 ++ app/webpacker/css/admin/all.scss | 1 - app/webpacker/css/admin/plugins/jstree.scss | 131 ----------------- app/webpacker/css/admin_v3/all.scss | 1 - config/locales/en.yml | 21 ++- config/routes/api.rb | 13 +- config/routes/spree.rb | 10 +- ...40806135838_drop_spree_taxonomies_table.rb | 14 ++ db/schema.rb | 15 -- lib/spree/core.rb | 5 - lib/tasks/sample_data/taxon_factory.rb | 9 +- .../api/v0/taxonomies_controller_spec.rb | 34 ----- .../api/v0/taxons_controller_spec.rb | 62 ++------ .../spree/admin/taxons_controller_spec.rb | 68 +++++++-- spec/factories/taxon_factory.rb | 2 - spec/factories/taxonomy_factory.rb | 7 - spec/models/spree/ability_spec.rb | 7 - spec/models/spree/taxon_spec.rb | 35 ----- spec/models/spree/taxonomy_spec.rb | 19 --- spec/queries/product_scope_query_spec.rb | 4 +- .../admin/configuration/taxonomies_spec.rb | 59 -------- .../system/consumer/shopping/products_spec.rb | 2 +- 48 files changed, 206 insertions(+), 908 deletions(-) delete mode 100644 app/assets/javascripts/admin/spree/taxons/taxon_tree_menu.js.coffee delete mode 100644 app/assets/javascripts/admin/spree/taxons/taxonomy.js.coffee delete mode 100644 app/controllers/api/v0/taxonomies_controller.rb delete mode 100644 app/controllers/spree/admin/taxonomies_controller.rb delete mode 100644 app/helpers/spree/admin/taxons_helper.rb delete mode 100644 app/models/spree/taxonomy.rb delete mode 100644 app/views/spree/admin/taxonomies/_form.html.haml delete mode 100755 app/views/spree/admin/taxonomies/_js_head.html.erb delete mode 100644 app/views/spree/admin/taxonomies/_list.html.haml delete mode 100644 app/views/spree/admin/taxonomies/_taxon.html.haml delete mode 100755 app/views/spree/admin/taxonomies/edit.haml delete mode 100644 app/views/spree/admin/taxonomies/index.html.haml delete mode 100644 app/views/spree/admin/taxonomies/new.html.haml create mode 100644 app/views/spree/admin/taxons/destroy_taxon.turbo_stream.haml create mode 100644 app/views/spree/admin/taxons/index.html.haml create mode 100644 app/views/spree/admin/taxons/new.html.haml delete mode 100644 app/webpacker/css/admin/plugins/jstree.scss create mode 100644 db/migrate/20240806135838_drop_spree_taxonomies_table.rb delete mode 100644 spec/controllers/api/v0/taxonomies_controller_spec.rb delete mode 100644 spec/factories/taxonomy_factory.rb delete mode 100644 spec/models/spree/taxonomy_spec.rb delete mode 100644 spec/system/admin/configuration/taxonomies_spec.rb diff --git a/Gemfile b/Gemfile index 94f0e62398..6acb4b3825 100644 --- a/Gemfile +++ b/Gemfile @@ -16,7 +16,6 @@ gem "image_processing" gem 'activemerchant', '>= 1.78.0' gem 'angular-rails-templates', '>= 0.3.0' -gem 'awesome_nested_set' gem 'ransack', '~> 4.1.0' gem 'responders' gem 'webpacker', '~> 5' diff --git a/app/assets/javascripts/admin/spree/taxons/taxon_tree_menu.js.coffee b/app/assets/javascripts/admin/spree/taxons/taxon_tree_menu.js.coffee deleted file mode 100644 index dca01a3aa3..0000000000 --- a/app/assets/javascripts/admin/spree/taxons/taxon_tree_menu.js.coffee +++ /dev/null @@ -1,21 +0,0 @@ -root = exports ? this - -root.taxon_tree_menu = (obj, context) -> - - base_url = Spree.url(Spree.routes.taxonomy_taxons) - admin_base_url = Spree.url(Spree.routes.admin_taxonomy_taxons) - edit_url = Spree.url(Spree.routes.admin_taxonomy_taxons + '/' + obj.attr("id") + "/edit"); - - create: - label: " " + Spree.translations.add, - action: (obj) -> context.create(obj) - rename: - label: " " + Spree.translations.rename, - action: (obj) -> context.rename(obj) - remove: - label: " " + Spree.translations.remove, - action: (obj) -> context.remove(obj) - edit: - separator_before: true, - label: " " + Spree.translations.edit, - action: (obj) -> window.location = edit_url.toString() diff --git a/app/assets/javascripts/admin/spree/taxons/taxonomy.js.coffee b/app/assets/javascripts/admin/spree/taxons/taxonomy.js.coffee deleted file mode 100644 index a5b01868b1..0000000000 --- a/app/assets/javascripts/admin/spree/taxons/taxonomy.js.coffee +++ /dev/null @@ -1,139 +0,0 @@ -handle_ajax_error = (XMLHttpRequest, textStatus, errorThrown) -> - $.jstree.rollback(last_rollback) - $("#ajax_error").show().html("" + server_error + "
" + taxonomy_tree_error) - -handle_move = (e, data) -> - last_rollback = data.rlbk - position = data.rslt.cp - node = data.rslt.o - new_parent = data.rslt.np - - url = new URL(Spree.routes.admin_taxonomy_taxons) - url.pathname = url.pathname + '/' + node.attr("id") - data = { - _method: "put", - "taxon[position]": position, - "taxon[parent_id]": if !isNaN(new_parent.attr("id")) then new_parent.attr("id") else undefined - } - $.ajax - type: "POST", - dataType: "json", - url: url.toString(), - data: data, - error: handle_ajax_error - - true - -handle_create = (e, data) -> - last_rollback = data.rlbk - node = data.rslt.obj - name = data.rslt.name - position = data.rslt.position - new_parent = data.rslt.parent - - data = { - "taxon[name]": name, - "taxon[position]": position - "taxon[parent_id]": if !isNaN(new_parent.attr("id")) then new_parent.attr("id") else undefined - } - $.ajax - type: "POST", - dataType: "json", - url: base_url.toString(), - data: data, - error: handle_ajax_error, - success: (data,result) -> - node.attr('id', data.id) - -handle_rename = (e, data) -> - last_rollback = data.rlbk - node = data.rslt.obj - name = data.rslt.new_name - # change the name inside the main input field as well if taxon is the root one - document.getElementById("taxonomy_name").value = name if node.parents("[id]").attr("id") == "taxonomy_tree" - - url = new URL(base_url) - url.pathname = url.pathname + '/' + node.attr("id") - - $.ajax - type: "POST", - dataType: "json", - url: url.toString(), - data: {_method: "put", "taxon[name]": name }, - error: handle_ajax_error - -handle_delete = (e, data) -> - last_rollback = data.rlbk - node = data.rslt.obj - delete_url = new URL(base_url) - delete_url.pathname = delete_url.pathname + '/' + node.attr("id") - if confirm(Spree.translations.are_you_sure_delete) - $.ajax - type: "POST", - dataType: "json", - url: delete_url.toString(), - data: {_method: "delete"}, - error: handle_ajax_error - else - $.jstree.rollback(last_rollback) - last_rollback = null - -root = exports ? this -root.setup_taxonomy_tree = (taxonomy_id) -> - if taxonomy_id != undefined - # this is defined within admin/taxonomies/edit - root.base_url = Spree.url(Spree.routes.taxonomy_taxons) - - $.ajax - url: base_url.pathname.replace("/taxons", "/jstree"), - success: (taxonomy) -> - last_rollback = null - - conf = - json_data: - data: taxonomy, - ajax: - url: (e) -> - base_url.pathname + '/' + e.attr('id') + '/jstree' - themes: - theme: "apple", - url: "/assets/jquery.jstree/themes/apple/style.css" - strings: - new_node: new_taxon, - loading: Spree.translations.loading + "..." - crrm: - move: - check_move: (m) -> - position = m.cp - node = m.o - new_parent = m.np - - # no parent or cant drag and drop - if !new_parent || node.attr("rel") == "root" - return false - - # can't drop before root - if new_parent.attr("id") == "taxonomy_tree" && position == 0 - return false - - true - contextmenu: - items: (obj) -> - taxon_tree_menu(obj, this) - plugins: ["themes", "json_data", "dnd", "crrm", "contextmenu"] - - $("#taxonomy_tree").jstree(conf) - .bind("move_node.jstree", handle_move) - .bind("remove.jstree", handle_delete) - .bind("create.jstree", handle_create) - .bind("rename.jstree", handle_rename) - .bind "loaded.jstree", -> - $(this).jstree("core").toggle_node($('.jstree-icon').first()) - - $("#taxonomy_tree a").on "dblclick", (e) -> - $("#taxonomy_tree").jstree("rename", this) - - # surpress form submit on enter/return - $(document).keypress (e) -> - if e.keyCode == 13 - e.preventDefault() diff --git a/app/controllers/api/v0/taxonomies_controller.rb b/app/controllers/api/v0/taxonomies_controller.rb deleted file mode 100644 index bf6077a9e1..0000000000 --- a/app/controllers/api/v0/taxonomies_controller.rb +++ /dev/null @@ -1,16 +0,0 @@ -# frozen_string_literal: true - -module Api - module V0 - class TaxonomiesController < Api::V0::BaseController - respond_to :json - - skip_authorization_check only: :jstree - - def jstree - @taxonomy = Spree::Taxonomy.find(params[:id]) - render json: @taxonomy.root, serializer: Api::TaxonJstreeSerializer - end - end - end -end diff --git a/app/controllers/api/v0/taxons_controller.rb b/app/controllers/api/v0/taxons_controller.rb index 75e85f5435..f5f8bf6275 100644 --- a/app/controllers/api/v0/taxons_controller.rb +++ b/app/controllers/api/v0/taxons_controller.rb @@ -5,12 +5,10 @@ module Api class TaxonsController < Api::V0::BaseController respond_to :json - skip_authorization_check only: [:index, :show, :jstree] + skip_authorization_check only: [:index, :show] def index - @taxons = if taxonomy - taxonomy.root.children - elsif params[:ids] + @taxons = if params[:ids] Spree::Taxon.where(id: raw_params[:ids].split(",")) else Spree::Taxon.ransack(raw_params[:q]).result @@ -18,23 +16,9 @@ module Api render json: @taxons, each_serializer: Api::TaxonSerializer end - def jstree - @taxon = taxon - render json: @taxon.children, each_serializer: Api::TaxonJstreeSerializer - end - def create authorize! :create, Spree::Taxon @taxon = Spree::Taxon.new(taxon_params) - @taxon.taxonomy_id = params[:taxonomy_id] - taxonomy = Spree::Taxonomy.find_by(id: params[:taxonomy_id]) - - if taxonomy.nil? - @taxon.errors.add(:taxonomy_id, I18n.t(:invalid_taxonomy_id, scope: 'spree.api')) - invalid_resource!(@taxon) && return - end - - @taxon.parent_id = taxonomy.root.id unless params.dig(:taxon, :parent_id) if @taxon.save render json: @taxon, serializer: Api::TaxonSerializer, status: :created @@ -60,20 +44,14 @@ module Api private - def taxonomy - return if params[:taxonomy_id].blank? - - @taxonomy ||= Spree::Taxonomy.find(params[:taxonomy_id]) - end - def taxon - @taxon ||= taxonomy.taxons.find(params[:id]) + @taxon = Spree::Taxon.find(params[:id]) end def taxon_params return if params[:taxon].blank? - params.require(:taxon).permit([:name, :parent_id, :position]) + params.require(:taxon).permit([:name, :position]) end end end diff --git a/app/controllers/spree/admin/taxonomies_controller.rb b/app/controllers/spree/admin/taxonomies_controller.rb deleted file mode 100644 index 68f65e000e..0000000000 --- a/app/controllers/spree/admin/taxonomies_controller.rb +++ /dev/null @@ -1,27 +0,0 @@ -# frozen_string_literal: true - -module Spree - module Admin - class TaxonomiesController < ::Admin::ResourceController - respond_to :json, only: [:get_children] - - def get_children - @taxons = Taxon.find(params[:parent_id]).children - end - - private - - def location_after_save - if @taxonomy.created_at == @taxonomy.updated_at - spree.edit_admin_taxonomy_url(@taxonomy) - else - spree.admin_taxonomies_url - end - end - - def permitted_resource_params - params.require(:taxonomy).permit(:name) - end - end - end -end diff --git a/app/controllers/spree/admin/taxons_controller.rb b/app/controllers/spree/admin/taxons_controller.rb index 9efc97ed79..e6020eb8b5 100644 --- a/app/controllers/spree/admin/taxons_controller.rb +++ b/app/controllers/spree/admin/taxons_controller.rb @@ -2,122 +2,70 @@ module Spree module Admin - class TaxonsController < Spree::Admin::BaseController - respond_to :html, :json, :js + class TaxonsController < ::Admin::ResourceController + before_action :set_taxon, except: %i[create index new] - def edit - @taxonomy = Taxonomy.find(params[:taxonomy_id]) - @taxon = @taxonomy.taxons.find(params[:id]) - @permalink_part = @taxon.permalink.split("/").last + def index + @taxons = Taxon.order(:name) end + def new + @taxon = Taxon.new + end + + def edit; end + def create - @taxonomy = Taxonomy.find(params[:taxonomy_id]) - @taxon = @taxonomy.taxons.build(params[:taxon]) + @taxon = Spree::Taxon.new(taxon_params) if @taxon.save - respond_with(@taxon) do |format| - format.json { render json: @taxon.to_json } - end + flash[:success] = flash_message_for(@taxon, :successfully_created) + redirect_to edit_admin_taxon_path(@taxon.id) else - flash[:error] = Spree.t('errors.messages.could_not_create_taxon') - respond_with(@taxon) do |format| - format.html do - if redirect_to @taxonomy - spree.edit_admin_taxonomy_url(@taxonomy) - else - spree.admin_taxonomies_url - end - end - end + render :new, status: :unprocessable_entity end end def update - @taxonomy = Taxonomy.find(params[:taxonomy_id]) - @taxon = @taxonomy.taxons.find(params[:id]) - parent_id = params[:taxon][:parent_id] - new_position = params[:taxon][:position] - - if parent_id || new_position # taxon is being moved - new_parent = parent_id.nil? ? @taxon.parent : Taxon.find(parent_id.to_i) - new_position = new_position.nil? ? -1 : new_position.to_i - - # Bellow is a very complicated way of finding where in nested set we - # should actually move the taxon to achieve sane results, - # JS is giving us the desired position, which was awesome for previous setup, - # but now it's quite complicated to find where we should put it as we have - # to differenciate between moving to the same branch, up down and into - # first position. - new_siblings = new_parent.children - if new_position <= 0 && new_siblings.empty? - @taxon.move_to_child_of(new_parent) - elsif new_parent.id != @taxon.parent_id - if new_position.zero? - @taxon.move_to_left_of(new_siblings.first) - else - @taxon.move_to_right_of(new_siblings[new_position - 1]) - end - elsif new_position < new_siblings.index(@taxon) - @taxon.move_to_left_of(new_siblings[new_position]) # we move up - else - @taxon.move_to_right_of(new_siblings[new_position - 1]) # we move down - end - # Reset legacy position, if any extensions still rely on it - new_parent.children.reload.each do |t| - t.update_columns( - position: t.position, - updated_at: Time.zone.now - ) - end - - if parent_id - @taxon.reload - @taxon.set_permalink - @taxon.save! - @update_children = true - end - end - - if params.key? "permalink_part" - parent_permalink = @taxon.permalink.split("/")[0...-1].join("/") - parent_permalink += "/" if parent_permalink.present? - params[:taxon][:permalink] = parent_permalink + params[:permalink_part] - end - # check if we need to rename child taxons if parent name or permalink changes - if params[:taxon][:name] != @taxon.name || params[:taxon][:permalink] != @taxon.permalink - @update_children = true - end - if @taxon.update(taxon_params) flash[:success] = flash_message_for(@taxon, :successfully_updated) - end - - # rename child taxons - if @update_children - @taxon.descendants.each do |taxon| - taxon.reload - taxon.set_permalink - taxon.save! - end - end - - respond_with(@taxon) do |format| - format.html { redirect_to spree.edit_admin_taxonomy_url(@taxonomy) } - format.json { render json: @taxon.to_json } + redirect_to edit_admin_taxon_path(@taxon.id) + else + render :edit, status: :unprocessable_entity end end def destroy - @taxon = Taxon.find(params[:id]) - @taxon.destroy - respond_with(@taxon) { |format| format.json { render json: '' } } + status = if @taxon.destroy + flash_message = t('.delete_taxon.success') + status = :ok + else + flash_message = t('.delete_taxon.error') + status = :unprocessable_entity + end + + respond_to do |format| + format.html { + flash[:success] = flash_message if status == :ok + flash[:error] = flash_message if status == :unprocessable_entity + redirect_to admin_taxons_path + } + format.turbo_stream { + flash[:success] = flash_message if status == :ok + flash[:error] = flash_message if status == :unprocessable_entity + render :destroy_taxon, status: + } + end end private + def set_taxon + @taxon = Taxon.find(params[:id]) + end + def taxon_params params.require(:taxon).permit( - :name, :parent_id, :position, :icon, :description, :permalink, :taxonomy_id, + :name, :position, :icon, :description, :permalink, :meta_description, :meta_keywords, :meta_title, :dfc_id ) end diff --git a/app/helpers/spree/admin/taxons_helper.rb b/app/helpers/spree/admin/taxons_helper.rb deleted file mode 100644 index e8e4d8d918..0000000000 --- a/app/helpers/spree/admin/taxons_helper.rb +++ /dev/null @@ -1,11 +0,0 @@ -# frozen_string_literal: true - -module Spree - module Admin - module TaxonsHelper - def taxon_path(taxon) - taxon.ancestors.reverse.collect(&:name).join( " >> ") - end - end - end -end diff --git a/app/models/spree/ability.rb b/app/models/spree/ability.rb index 4e2a8601be..0de4b5abbd 100644 --- a/app/models/spree/ability.rb +++ b/app/models/spree/ability.rb @@ -39,7 +39,6 @@ module Spree can [:index, :read], StockLocation can [:index, :read], StockMovement can [:index, :read], Taxon - can [:index, :read], Taxonomy can [:index, :read], Variant can [:index, :read], Zone end diff --git a/app/models/spree/taxon.rb b/app/models/spree/taxon.rb index bc0b85371a..50a55ce6d9 100644 --- a/app/models/spree/taxon.rb +++ b/app/models/spree/taxon.rb @@ -2,12 +2,6 @@ module Spree class Taxon < ApplicationRecord - self.belongs_to_required_by_default = false - - acts_as_nested_set dependent: :destroy - - belongs_to :taxonomy, class_name: 'Spree::Taxonomy', touch: true - has_many :variants, class_name: "Spree::Variant", foreign_key: "primary_taxon_id", inverse_of: :primary_taxon, dependent: :restrict_with_error @@ -32,11 +26,7 @@ module Spree end def set_permalink - if parent.present? - self.permalink = [parent.permalink, permalink_end].join('/') - elsif permalink.blank? - self.permalink = UrlGenerator.to_url(name) - end + self.permalink = UrlGenerator.to_url(name) end # For #2759 @@ -44,13 +34,6 @@ module Spree permalink end - def pretty_name - ancestor_chain = ancestors.inject("") do |name, ancestor| - name + "#{ancestor.name} -> " - end - ancestor_chain + name.to_s - end - # Find all the taxons of supplied products for each enterprise, indexed by enterprise. # Format: {enterprise_id => [taxon_id, ...]} def self.supplied_taxons diff --git a/app/models/spree/taxonomy.rb b/app/models/spree/taxonomy.rb deleted file mode 100644 index a8600caa27..0000000000 --- a/app/models/spree/taxonomy.rb +++ /dev/null @@ -1,27 +0,0 @@ -# frozen_string_literal: true - -module Spree - class Taxonomy < ApplicationRecord - validates :name, presence: true - - has_many :taxons, dependent: :nullify - has_one :root, -> { where parent_id: nil }, class_name: "Spree::Taxon", dependent: :destroy - - after_save :set_name - - default_scope -> { order("#{table_name}.position") } - - private - - def set_name - if root - root.update_columns( - name:, - updated_at: Time.zone.now - ) - else - self.root = Taxon.create!(taxonomy_id: id, name:) - end - end - end -end diff --git a/app/serializers/api/admin/taxon_serializer.rb b/app/serializers/api/admin/taxon_serializer.rb index cb18665f56..1a101d4cdc 100644 --- a/app/serializers/api/admin/taxon_serializer.rb +++ b/app/serializers/api/admin/taxon_serializer.rb @@ -3,7 +3,7 @@ module Api module Admin class TaxonSerializer < ActiveModel::Serializer - attributes :id, :name, :pretty_name + attributes :id, :name end end end diff --git a/app/serializers/api/taxon_serializer.rb b/app/serializers/api/taxon_serializer.rb index 38eb2bf3dd..fc986a6708 100644 --- a/app/serializers/api/taxon_serializer.rb +++ b/app/serializers/api/taxon_serializer.rb @@ -4,5 +4,5 @@ class Api::TaxonSerializer < ActiveModel::Serializer cached delegate :cache_key, to: :object - attributes :id, :name, :permalink, :pretty_name, :position, :parent_id, :taxonomy_id + attributes :id, :name, :permalink, :position end diff --git a/app/views/spree/admin/shared/_configuration_menu.html.haml b/app/views/spree/admin/shared/_configuration_menu.html.haml index 215011f6b6..0b0f2c2d80 100644 --- a/app/views/spree/admin/shared/_configuration_menu.html.haml +++ b/app/views/spree/admin/shared/_configuration_menu.html.haml @@ -15,7 +15,7 @@ - if DefaultCountry.id = configurations_sidebar_menu_item Spree.t(:states), admin_country_states_path(DefaultCountry.id) = configurations_sidebar_menu_item Spree.t(:payment_methods), admin_payment_methods_path - = configurations_sidebar_menu_item Spree.t(:taxonomies), admin_taxonomies_path + = configurations_sidebar_menu_item Spree.t(:taxons), admin_taxons_path = configurations_sidebar_menu_item Spree.t(:shipping_methods), admin_shipping_methods_path = configurations_sidebar_menu_item Spree.t(:shipping_categories), admin_shipping_categories_path = configurations_sidebar_menu_item t(:enterprise_fees), main_app.admin_enterprise_fees_path diff --git a/app/views/spree/admin/shared/_tabs.html.haml b/app/views/spree/admin/shared/_tabs.html.haml index f9a9c6613f..3ad1a1d53a 100644 --- a/app/views/spree/admin/shared/_tabs.html.haml +++ b/app/views/spree/admin/shared/_tabs.html.haml @@ -3,7 +3,7 @@ = tab :order_cycles, url: main_app.admin_order_cycles_path, icon: 'icon-refresh' = tab :orders, :subscriptions, :customer_details, :adjustments, :payments, :return_authorizations, url: admin_orders_path, icon: 'icon-shopping-cart' = tab :reports, url: main_app.admin_reports_path, icon: 'icon-file' -= tab :general_settings, :terms_of_service_files, :mail_methods, :tax_categories, :tax_rates, :tax_settings, :zones, :countries, :states, :payment_methods, :taxonomies, :shipping_methods, :shipping_categories, :enterprise_fees, :contents, :invoice_settings, :matomo_settings, :stripe_connect_settings, :connected_app_settings, label: 'configuration', icon: 'icon-wrench', url: edit_admin_general_settings_path += tab :general_settings, :terms_of_service_files, :mail_methods, :tax_categories, :tax_rates, :tax_settings, :zones, :countries, :states, :payment_methods, :taxons, :shipping_methods, :shipping_categories, :enterprise_fees, :contents, :invoice_settings, :matomo_settings, :stripe_connect_settings, :connected_app_settings, label: 'configuration', icon: 'icon-wrench', url: edit_admin_general_settings_path = tab :enterprises, :enterprise_relationships, :vouchers, :oidc_settings, url: main_app.admin_enterprises_path = tab :customers, url: main_app.admin_customers_path = tab :enterprise_groups, url: main_app.admin_enterprise_groups_path, label: 'groups' diff --git a/app/views/spree/admin/taxonomies/_form.html.haml b/app/views/spree/admin/taxonomies/_form.html.haml deleted file mode 100644 index ac60030374..0000000000 --- a/app/views/spree/admin/taxonomies/_form.html.haml +++ /dev/null @@ -1,7 +0,0 @@ -.field.align-center - = f.field_container :name do - = f.label :name, t("spree.name") - %span.required * - %br/ - = error_message_on :taxonomy, :name - = text_field :taxonomy, :name diff --git a/app/views/spree/admin/taxonomies/_js_head.html.erb b/app/views/spree/admin/taxonomies/_js_head.html.erb deleted file mode 100755 index e95f4bdeff..0000000000 --- a/app/views/spree/admin/taxonomies/_js_head.html.erb +++ /dev/null @@ -1,13 +0,0 @@ -<% content_for :head do %> - <%= javascript_tag "var taxonomy_id = #{@taxonomy.id}; - var loading = '#{escape_javascript t("spree.loading")}'; - var new_taxon = '#{escape_javascript t("spree.new_taxon")}'; - var server_error = '#{escape_javascript t("spree.server_error")}'; - var taxonomy_tree_error = '#{escape_javascript t("spree.taxonomy_tree_error")}'; - - $(document).ready(function(){ - setup_taxonomy_tree(taxonomy_id); - }); - " - %> -<% end %> diff --git a/app/views/spree/admin/taxonomies/_list.html.haml b/app/views/spree/admin/taxonomies/_list.html.haml deleted file mode 100644 index 1574e8b718..0000000000 --- a/app/views/spree/admin/taxonomies/_list.html.haml +++ /dev/null @@ -1,19 +0,0 @@ -%table#listing_taxonomies.index.sortable{"data-sortable-link" => update_positions_admin_taxonomies_url} - %colgroup - %col{style: "width: 85%"}/ - %col{style: "width: 15%"}/ - %thead - %tr - %th= t("spree.name") - %th.actions - %tbody - - @taxonomies.each do |taxonomy| - - tr_class = cycle('odd', 'even') - - tr_id = spree_dom_id(taxonomy) - %tr{class: tr_class, id: tr_id} - %td - %span.handle - = taxonomy.name - %td.actions - = link_to_edit taxonomy.id, no_text: true - = link_to_delete taxonomy, no_text: true diff --git a/app/views/spree/admin/taxonomies/_taxon.html.haml b/app/views/spree/admin/taxonomies/_taxon.html.haml deleted file mode 100644 index ce8fd21247..0000000000 --- a/app/views/spree/admin/taxonomies/_taxon.html.haml +++ /dev/null @@ -1,7 +0,0 @@ -- if taxon.children.length != 0 - %ul - - taxon.children.each do |child| - %li{id: "#{child.id}", rel: "taxon"} - %a{href: "#"}= child.name - - if child.children.length > 0 - = render partial: 'taxon', locals: { taxon: child } diff --git a/app/views/spree/admin/taxonomies/edit.haml b/app/views/spree/admin/taxonomies/edit.haml deleted file mode 100755 index 2b8065674e..0000000000 --- a/app/views/spree/admin/taxonomies/edit.haml +++ /dev/null @@ -1,27 +0,0 @@ -= render partial: 'spree/admin/shared/configuration_menu' - -= render partial: 'js_head' - -- content_for :page_title do - = t("spree.taxonomy_edit") - -- content_for :page_actions do - %li - = button_link_to t("spree.back_to_taxonomies_list"), spree.admin_taxonomies_path, icon: 'icon-arrow-left' - -#ajax_error.errorExplanation{style: "display:none;"} -= form_for [:admin, @taxonomy] do |f| - %fieldset.no-border-top - = render partial: 'form', locals: { f: f } - %div - = label_tag nil, t("spree.tree") - %br/ - :javascript - Spree.routes.taxonomy_taxons = "#{main_app.api_v0_taxonomy_taxons_url(@taxonomy)}"; - Spree.routes.admin_taxonomy_taxons = "#{spree.admin_taxonomy_taxons_url(@taxonomy)}"; - #taxonomy_tree.tree - .info= t("spree.taxonomy_tree_instruction") - %br/ - .filter-actions.actions - = button t('spree.actions.update'), 'icon-refresh' - = button_link_to t('spree.actions.cancel'), admin_taxonomies_path, icon: 'icon-remove' diff --git a/app/views/spree/admin/taxonomies/index.html.haml b/app/views/spree/admin/taxonomies/index.html.haml deleted file mode 100644 index 63041138d1..0000000000 --- a/app/views/spree/admin/taxonomies/index.html.haml +++ /dev/null @@ -1,11 +0,0 @@ -= render partial: 'spree/admin/shared/configuration_menu' - -- content_for :page_title do - = t("spree.taxonomies") - -- content_for :page_actions do - %li - = button_link_to t("spree.new_taxonomy"), spree.new_admin_taxonomy_url, icon: 'icon-plus', id: 'admin_new_taxonomy_link' - -#list-taxonomies - = render partial: 'list' diff --git a/app/views/spree/admin/taxonomies/new.html.haml b/app/views/spree/admin/taxonomies/new.html.haml deleted file mode 100644 index a84338ddf1..0000000000 --- a/app/views/spree/admin/taxonomies/new.html.haml +++ /dev/null @@ -1,15 +0,0 @@ -= render partial: 'spree/admin/shared/configuration_menu' - -- content_for :page_title do - = t("spree.new_taxonomy") - -- content_for :page_actions do - %li - = button_link_to t("spree.back_to_taxonomies_list"), spree.admin_taxonomies_path, icon: 'icon-arrow-left' - -= form_for [:admin, @taxonomy] do |f| - = render partial: 'form', locals: { f: f } - %fieldset.no-border-top - %br/ - .filter-actions.actions - = button t("spree.create"), 'icon-ok' diff --git a/app/views/spree/admin/taxons/_form.html.haml b/app/views/spree/admin/taxons/_form.html.haml index 621b5eadf8..d58e0ae907 100644 --- a/app/views/spree/admin/taxons/_form.html.haml +++ b/app/views/spree/admin/taxons/_form.html.haml @@ -10,7 +10,8 @@ = f.label :permalink_part, t(".permalink") %span.required * %br/ - = @taxon.permalink.split("/")[0...-1].join("/") + "/" + - if @taxon.permalink + = @taxon.permalink.split("/")[0...-1].join("/") + "/" = text_field_tag :permalink_part, @permalink_part = f.field_container :meta_title do = f.label :meta_title, t(".meta_title") diff --git a/app/views/spree/admin/taxons/destroy_taxon.turbo_stream.haml b/app/views/spree/admin/taxons/destroy_taxon.turbo_stream.haml new file mode 100644 index 0000000000..6eb0c41014 --- /dev/null +++ b/app/views/spree/admin/taxons/destroy_taxon.turbo_stream.haml @@ -0,0 +1,4 @@ +- unless flash[:error] + = turbo_stream.remove(spree_dom_id(@taxon)) += turbo_stream.append "flashes" do + = render(partial: 'admin/shared/flashes', locals: { flashes: flash }) \ No newline at end of file diff --git a/app/views/spree/admin/taxons/edit.html.haml b/app/views/spree/admin/taxons/edit.html.haml index 1df6e7288f..25c2815d07 100644 --- a/app/views/spree/admin/taxons/edit.html.haml +++ b/app/views/spree/admin/taxons/edit.html.haml @@ -1,16 +1,16 @@ = render partial: 'spree/admin/shared/configuration_menu' - content_for :page_title do - = t("spree.taxonomy_edit") + = t(".title") - content_for :page_actions do %li - = button_link_to t("spree.back_to_taxonomies_list"), spree.admin_taxonomies_path, icon: 'icon-arrow-left' + = button_link_to t("spree.admin.taxons.back_to_list"), admin_taxons_path, icon: 'icon-arrow-left' -- # Because otherwise the form would attempt to use to_param of @taxon -- form_url = admin_taxonomy_taxon_path(@taxonomy.id, @taxon.id) -= form_for [:admin, @taxonomy, @taxon], method: :put, url: form_url, html: { multipart: true } do |f| += form_with model: @taxon, url: admin_taxon_path(@taxon.id), + data: { turbo: true }, id: "edit-taxon-#{@taxon.id}", + method: :put, html: { multipart: true } do |f| = render partial: 'form', locals: { f: f } .form-buttons = button t('spree.actions.update'), 'icon-refresh' - = button_link_to t('spree.actions.cancel'), edit_admin_taxonomy_url(@taxonomy), icon: "icon-remove" + = button_link_to t('spree.actions.cancel'), admin_taxons_url, icon: "icon-remove" diff --git a/app/views/spree/admin/taxons/index.html.haml b/app/views/spree/admin/taxons/index.html.haml new file mode 100644 index 0000000000..c4f673421e --- /dev/null +++ b/app/views/spree/admin/taxons/index.html.haml @@ -0,0 +1,27 @@ += render partial: 'spree/admin/shared/configuration_menu' + +- content_for :page_title do + = t(".title") + +- content_for :page_actions do + %li + = button_link_to t(".new_taxon"), spree.new_admin_taxon_url, icon: 'icon-plus', id: 'admin_new_taxon_link' + +%table#listing_taxons.index + %colgroup + %col{style: "width: 85%"}/ + %col{style: "width: 15%"}/ + %thead + %tr + %th= t("spree.name") + %th.actions + %tbody + - @taxons.each do |taxon| + %tr{class: cycle('odd', 'even'), id: spree_dom_id(taxon)} + %td + = taxon.name + %td.actions + = link_to_edit taxon.id, no_text: true + = link_to '', admin_taxon_path(taxon.id), method: :delete, + class: "icon_link with-tip icon-trash no-text", + data: { turbo: true, turbo_method: :delete, turbo_confirm: t(:are_you_sure) } diff --git a/app/views/spree/admin/taxons/new.html.haml b/app/views/spree/admin/taxons/new.html.haml new file mode 100644 index 0000000000..38930fbcdf --- /dev/null +++ b/app/views/spree/admin/taxons/new.html.haml @@ -0,0 +1,14 @@ += render partial: 'spree/admin/shared/configuration_menu' + +- content_for :page_title do + = t(".title") + +- content_for :page_actions do + %li + = button_link_to t("spree.admin.taxons.back_to_list"), spree.admin_taxons_path, icon: 'icon-arrow-left' + += form_with model: @taxon, url: admin_taxons_path, data: { turbo: true }, html: { multipart: true } do |f| + = render partial: 'form', locals: { f: f } + .form-buttons + = button t('actions.create'), 'icon-ok' + = button_link_to t('actions.cancel'), admin_taxons_url, icon: "icon-remove" diff --git a/app/webpacker/css/admin/all.scss b/app/webpacker/css/admin/all.scss index d9ad7c0008..0534149116 100644 --- a/app/webpacker/css/admin/all.scss +++ b/app/webpacker/css/admin/all.scss @@ -34,7 +34,6 @@ @import "plugins/flatpickr-customization"; @import "plugins/powertip"; -@import "plugins/jstree"; @import "plugins/select2"; @import "sections/orders"; diff --git a/app/webpacker/css/admin/plugins/jstree.scss b/app/webpacker/css/admin/plugins/jstree.scss deleted file mode 100644 index b16fd23a1a..0000000000 --- a/app/webpacker/css/admin/plugins/jstree.scss +++ /dev/null @@ -1,131 +0,0 @@ -#taxonomy_tree { - > ul, - .jstree-icon { - background-image: none; - } - - .jstree-icon { - @extend [class^="icon-"], :before; - } - - .jstree-open > .jstree-icon { - @extend .icon-caret-down; - } - .jstree-closed > .jstree-icon { - @extend .icon-caret-right; - } - - li { - background-image: none; - - a { - background-color: very-light($color-3); - border: 1px solid $color-border; - color: $color-body-text; - font-weight: $font-weight-bold; - text-shadow: none; - width: 90%; - height: auto; - line-height: inherit; - padding: 5px 0 5px 10px; - margin-bottom: 10px; - - .jstree-icon { - padding-left: 0px; - @extend .icon-move; - } - } - } -} - -#vakata-dragged.jstree-apple .jstree-invalid, -#vakata-dragged.jstree-apple .jstree-ok, -#jstree-marker { - background-image: none !important; - background-color: transparent !important; - @extend [class^="icon-"], :before; -} -#vakata-dragged.jstree-apple .jstree-invalid { - @extend .icon-remove; - color: $color-5; -} -#vakata-dragged.jstree-apple .jstree-ok { - @extend .icon-ok; - color: $color-2; -} - -#jstree-marker { - @extend .icon-caret-right; - color: $color-body-text !important; - width: 4px !important; -} - -#jstree-marker-line { - @include border-radius($border-radius !important); - height: 0px !important; - margin-left: 5px !important; - margin-top: -2px !important; - border: none !important; - border-bottom: 1px solid $color-body-text !important; - background-color: very-light($color-3) !important; - - -webkit-box-shadow: none !important; - -moz-box-shadow: none !important; - box-shadow: none !important; -} - -#vakata-contextmenu { - background-color: $color-3 !important; - -moz-box-shadow: none !important; - -webkit-box-shadow: none !important; - box-shadow: none !important; - border: none !important; - @include border-radius($border-radius !important); - - &:before { - content: ""; - position: absolute; - border-left: 10px solid transparent; - border-right: 10px solid transparent; - border-bottom: 10px solid $color-3; - top: 0px; - margin-top: -10px; - left: 25px; - z-index: 1; - } - - a { - color: $color-1 !important; - line-height: inherit !important; - padding: 5px 10px !important; - margin: 0 !important; - font-size: 90% !important; - - &:hover { - @include border-radius($border-radius !important); - background-color: $color-2 !important; - border: none !important; - -moz-box-shadow: none !important; - -webkit-box-shadow: none !important; - line-height: inherit !important; - padding: 5px 10px !important; - margin: 0 !important; - } - } - - li:first-child a:hover:before { - content: ""; - position: absolute; - border-left: 10px solid transparent; - border-right: 10px solid transparent; - border-bottom: 10px solid $color-2; - top: 0px; - margin-top: -10px; - left: 25px; - z-index: 1; - } - - li.vakata-separator { - display: none; - } -} diff --git a/app/webpacker/css/admin_v3/all.scss b/app/webpacker/css/admin_v3/all.scss index 7ee27d28ac..bdba91568c 100644 --- a/app/webpacker/css/admin_v3/all.scss +++ b/app/webpacker/css/admin_v3/all.scss @@ -39,7 +39,6 @@ @import "plugins/flatpickr-customization"; // admin_v3 @import "plugins/powertip"; // admin_v3 -@import "../admin/plugins/jstree"; @import "sections/orders"; // admin_v3 @import "../admin/sections/products"; diff --git a/config/locales/en.yml b/config/locales/en.yml index 74e5b24b68..f43a318cb6 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -3959,10 +3959,6 @@ See the %{link} to find out more about %{sitename}'s features and to start using delivery: "Signed, sealed, delivered" start_date: "Start date" successfully_removed: "Successfully Removed" - taxonomy_edit: "Taxonomy edit" - taxonomy_tree_error: "There was an error updating the taxonomy tree." - taxonomy_tree_instruction: "Right-click on an item to add, rename, remove or edit." - tree: "Tree" updating: "Updating" your_order_is_empty_add_product: "Your order is empty, please search for and add a product above" add_product: "Add Product" @@ -4086,10 +4082,7 @@ See the %{link} to find out more about %{sitename}'s features and to start using new_state: "New State" payment_methods: "Payment Methods" - - taxonomies: "Taxonomies" - new_taxonomy: "New Taxonomy" - back_to_taxonomies_list: "Back to Taxonomies List" + taxons: "Product Categories" shipping_methods: "Shipping Methods" shipping_method: "Shipping Method" @@ -4582,6 +4575,18 @@ See the %{link} to find out more about %{sitename}'s features and to start using total: "Total" billing_address_name: "Name" taxons: + back_to_list: "Back to Product Categeory List" + index: + title: "Product Categories" + new_taxon: 'New product category' + new: + title: "New Product Category" + edit: + title: "Edit Product Category" + destroy: + delete_taxon: + success: "Successfully deleted the product category" + error: "Unable to delete the product category" form: name: Name permalink: Permalink diff --git a/config/routes/api.rb b/config/routes/api.rb index 74f2ad3652..30bd7cd1b1 100644 --- a/config/routes/api.rb +++ b/config/routes/api.rb @@ -79,18 +79,7 @@ Openfoodnetwork::Application.routes.draw do resources :states, :only => [:index, :show] - resources :taxons, :only => [:index] - - resources :taxonomies do - member do - get :jstree - end - resources :taxons do - member do - get :jstree - end - end - end + resources :taxons, except: %i[show edit] get '/reports/:report_type(/:report_subtype)', to: 'reports#show', constraints: lambda { |_| OpenFoodNetwork::FeatureToggle.enabled?(:api_reports) } diff --git a/config/routes/spree.rb b/config/routes/spree.rb index f59e4e7dca..e9d21a66cb 100644 --- a/config/routes/spree.rb +++ b/config/routes/spree.rb @@ -142,15 +142,7 @@ Spree::Core::Engine.routes.draw do end resources :states - resources :taxonomies do - collection do - post :update_positions - end - member do - get :get_children - end - resources :taxons - end + resources :taxons, except: :show resources :tax_rates resource :tax_settings diff --git a/db/migrate/20240806135838_drop_spree_taxonomies_table.rb b/db/migrate/20240806135838_drop_spree_taxonomies_table.rb new file mode 100644 index 0000000000..2a3fdd3d8f --- /dev/null +++ b/db/migrate/20240806135838_drop_spree_taxonomies_table.rb @@ -0,0 +1,14 @@ +class DropSpreeTaxonomiesTable < ActiveRecord::Migration[7.0] + def change + # Remove columns + remove_column :spree_taxons, :lft + remove_column :spree_taxons, :rgt + + # Remove references + remove_reference :spree_taxons, :parent, index: true, foriegn_key: true + remove_reference :spree_taxons, :taxonomy, index: true, foriegn_key: true + + # Drop table + drop_table :spree_taxonomies + end +end diff --git a/db/schema.rb b/db/schema.rb index 0ba99991e3..c312d7a913 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -878,31 +878,18 @@ ActiveRecord::Schema[7.0].define(version: 2024_08_10_150912) do t.datetime "deleted_at", precision: nil end - create_table "spree_taxonomies", id: :serial, force: :cascade do |t| - t.string "name", limit: 255, null: false - t.datetime "created_at", precision: nil, null: false - t.datetime "updated_at", precision: nil, null: false - t.integer "position", default: 0 - end - create_table "spree_taxons", id: :serial, force: :cascade do |t| - t.integer "parent_id" t.integer "position", default: 0 t.string "name", limit: 255, null: false t.string "permalink", limit: 255 - t.integer "taxonomy_id" t.datetime "created_at", precision: nil, null: false t.datetime "updated_at", precision: nil, null: false - t.integer "lft" - t.integer "rgt" t.text "description" t.string "meta_title", limit: 255 t.string "meta_description", limit: 255 t.string "meta_keywords", limit: 255 t.string "dfc_id" - t.index ["parent_id"], name: "index_taxons_on_parent_id" t.index ["permalink"], name: "index_taxons_on_permalink" - t.index ["taxonomy_id"], name: "index_taxons_on_taxonomy_id" end create_table "spree_tokenized_permissions", id: :serial, force: :cascade do |t| @@ -1220,8 +1207,6 @@ ActiveRecord::Schema[7.0].define(version: 2024_08_10_150912) do add_foreign_key "spree_stock_movements", "spree_stock_items", column: "stock_item_id" add_foreign_key "spree_tax_rates", "spree_tax_categories", column: "tax_category_id", name: "spree_tax_rates_tax_category_id_fk" add_foreign_key "spree_tax_rates", "spree_zones", column: "zone_id", name: "spree_tax_rates_zone_id_fk" - add_foreign_key "spree_taxons", "spree_taxonomies", column: "taxonomy_id", name: "spree_taxons_taxonomy_id_fk" - add_foreign_key "spree_taxons", "spree_taxons", column: "parent_id", name: "spree_taxons_parent_id_fk" add_foreign_key "spree_users", "spree_addresses", column: "bill_address_id", name: "spree_users_bill_address_id_fk" add_foreign_key "spree_users", "spree_addresses", column: "ship_address_id", name: "spree_users_ship_address_id_fk" add_foreign_key "spree_variants", "enterprises", column: "supplier_id" diff --git a/lib/spree/core.rb b/lib/spree/core.rb index 4c39461913..e50c687f2f 100644 --- a/lib/spree/core.rb +++ b/lib/spree/core.rb @@ -2,7 +2,6 @@ require 'active_merchant' require 'acts_as_list' -require 'awesome_nested_set' require 'cancan' require 'pagy' require 'mail' @@ -35,7 +34,3 @@ require 'spree/core/permalinks' require 'spree/core/token_resource' require 'spree/core/product_duplicator' require 'spree/core/gateway_error' - -ActiveRecord::Base.class_eval do - include CollectiveIdea::Acts::NestedSet -end diff --git a/lib/tasks/sample_data/taxon_factory.rb b/lib/tasks/sample_data/taxon_factory.rb index d9545c24d1..46e26c396e 100644 --- a/lib/tasks/sample_data/taxon_factory.rb +++ b/lib/tasks/sample_data/taxon_factory.rb @@ -8,23 +8,20 @@ module SampleData def create_samples log "Creating taxonomies:" - taxonomy = Spree::Taxonomy.find_or_create_by!(name: 'Products') taxons = ['Vegetables', 'Fruit', 'Oils', 'Preserves and Sauces', 'Dairy', 'Fungi'] taxons.each do |taxon_name| - create_taxon(taxonomy, taxon_name) + create_taxon(taxon_name) end end private - def create_taxon(taxonomy, taxon_name) + def create_taxon(taxon_name) return if Spree::Taxon.where(name: taxon_name).exists? log "- #{taxon_name}" Spree::Taxon.create!( - name: taxon_name, - parent_id: taxonomy.root.id, - taxonomy_id: taxonomy.id + name: taxon_name ) end end diff --git a/spec/controllers/api/v0/taxonomies_controller_spec.rb b/spec/controllers/api/v0/taxonomies_controller_spec.rb deleted file mode 100644 index 3426342ed5..0000000000 --- a/spec/controllers/api/v0/taxonomies_controller_spec.rb +++ /dev/null @@ -1,34 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -module Api - RSpec.describe V0::TaxonomiesController do - render_views - - let(:taxonomy) { create(:taxonomy) } - let(:taxon) { create(:taxon, name: "Ruby", taxonomy:) } - let(:taxon2) { create(:taxon, name: "Rails", taxonomy:) } - let(:attributes) { [:id, :name] } - - before do - allow(controller).to receive(:spree_current_user) { current_api_user } - - taxon2.children << create(:taxon, name: "3.2.2", taxonomy:) - taxon.children << taxon2 - taxonomy.root.children << taxon - end - - context "as a normal user" do - let(:current_api_user) { build(:user) } - - it "gets the jstree-friendly version of a taxonomy" do - api_get :jstree, id: taxonomy.id - - expect(json_response["data"]).to eq(taxonomy.root.name) - expect(json_response["attr"]).to eq("id" => taxonomy.root.id, "name" => taxonomy.root.name) - expect(json_response["state"]).to eq("closed") - end - end - end -end diff --git a/spec/controllers/api/v0/taxons_controller_spec.rb b/spec/controllers/api/v0/taxons_controller_spec.rb index 2c333027e9..a41f7c7d75 100644 --- a/spec/controllers/api/v0/taxons_controller_spec.rb +++ b/spec/controllers/api/v0/taxons_controller_spec.rb @@ -5,30 +5,19 @@ require 'spec_helper' RSpec.describe Api::V0::TaxonsController do render_views - let(:taxonomy) { create(:taxonomy) } - let(:taxon) { create(:taxon, name: "Ruby", taxonomy:) } - let(:taxon2) { create(:taxon, name: "Rails", taxonomy:) } - let(:attributes) { - ["id", "name", "pretty_name", "permalink", "position", "parent_id", "taxonomy_id"] + let!(:taxon) { create(:taxon, name: "Ruby") } + let!(:taxon2) { create(:taxon, name: "Rails") } + let!(:attributes) { + ["id", "name", "permalink", "position"] } before do allow(controller).to receive(:spree_current_user) { current_api_user } - - taxon2.children << create(:taxon, name: "3.2.2", taxonomy:) - taxon.children << taxon2 - taxonomy.root.children << taxon end context "as a normal user" do let(:current_api_user) { build(:user) } - it "gets all taxons for a taxonomy" do - api_get :index, taxonomy_id: taxonomy.id - - expect(json_response.first['name']).to eq taxon.name - end - it "gets all taxons" do api_get :index @@ -43,31 +32,21 @@ RSpec.describe Api::V0::TaxonsController do expect(json_response.first['name']).to eq "Ruby" end - it "gets all taxons in JSTree form" do - api_get :jstree, taxonomy_id: taxonomy.id, id: taxon.id - - response = json_response.first - expect(response["data"]).to eq(taxon2.name) - expect(response["attr"]).to eq("name" => taxon2.name, "id" => taxon2.id) - expect(response["state"]).to eq("closed") - end - it "cannot create a new taxon if not an admin" do - api_post :create, taxonomy_id: taxonomy.id, taxon: { name: "Location" } + api_post :create, taxon: { name: "Location" } assert_unauthorized! end it "cannot update a taxon" do - api_put :update, taxonomy_id: taxonomy.id, - id: taxon.id, + api_put :update, id: taxon.id, taxon: { name: "I hacked your store!" } assert_unauthorized! end it "cannot delete a taxon" do - api_delete :destroy, taxonomy_id: taxonomy.id, id: taxon.id + api_delete :destroy, id: taxon.id assert_unauthorized! end @@ -77,42 +56,25 @@ RSpec.describe Api::V0::TaxonsController do let(:current_api_user) { build(:admin_user) } it "can create" do - api_post :create, taxonomy_id: taxonomy.id, taxon: { name: "Colors" } + api_post :create, taxon: { name: "Colors" } expect(attributes.all? { |a| json_response.include? a }).to be true expect(response.status).to eq(201) - - expect(taxonomy.reload.root.children.count).to eq 2 - - expect(Spree::Taxon.last.parent_id).to eq taxonomy.root.id - expect(Spree::Taxon.last.taxonomy_id).to eq taxonomy.id end it "cannot create a new taxon with invalid attributes" do - api_post :create, taxonomy_id: taxonomy.id, taxon: {} + api_post :create, taxon: {} expect(response.status).to eq(422) expect(json_response["error"]).to eq("Invalid resource. Please fix errors and try again.") errors = json_response["errors"] - expect(taxonomy.reload.root.children.count).to eq 1 - end - - it "cannot create a new taxon with invalid taxonomy_id" do - api_post :create, taxonomy_id: 1000, taxon: { name: "Colors" } - - expect(response.status).to eq(422) - expect(json_response["error"]).to eq("Invalid resource. Please fix errors and try again.") - - errors = json_response["errors"] - expect(errors["taxonomy_id"]).not_to be_nil - expect(errors["taxonomy_id"].first).to eq "Invalid taxonomy id." - - expect(taxonomy.reload.root.children.count).to eq 1 + expect(Spree::Taxon.last).to eq taxon2 + expect(errors['name']).to eq ["can't be blank"] end it "can destroy" do - api_delete :destroy, taxonomy_id: taxonomy.id, id: taxon2.id + api_delete :destroy, id: taxon2.id expect(response.status).to eq(204) end diff --git a/spec/controllers/spree/admin/taxons_controller_spec.rb b/spec/controllers/spree/admin/taxons_controller_spec.rb index 3abc6669aa..df6c398cbd 100644 --- a/spec/controllers/spree/admin/taxons_controller_spec.rb +++ b/spec/controllers/spree/admin/taxons_controller_spec.rb @@ -5,28 +5,68 @@ require 'spec_helper' RSpec.describe Spree::Admin::TaxonsController do render_views - let(:taxonomy) { create(:taxonomy) } - let(:taxon) { create(:taxon, name: "Ruby", taxonomy:) } - let(:taxon2) { create(:taxon, name: "Rails", taxonomy:) } + let!(:taxon) { create(:taxon, name: "Ruby") } + let!(:taxon2) { create(:taxon, name: "Rails") } + let(:valid_attributes) { attributes_for(:taxon) } before do allow(controller).to receive(:spree_current_user) { current_api_user } - - taxonomy.root.children << taxon - taxonomy.root.children << taxon2 end - context "as an admin" do + describe 'admin user' do let(:current_api_user) { build(:admin_user) } - it "can reorder taxons" do - spree_post :update, - taxonomy_id: taxonomy.id, - id: taxon2.id, - taxon: { parent_id: taxonomy.root.id, position: 0 } + it "can view all taxons" do + spree_get :index - expect(taxon2.reload.lft).to eq 2 - expect(Spree::Taxonomy.find(taxonomy.id).root.children.first).to eq(taxon2) + expect(response).to have_http_status :ok + end + + it "open taxon edit form" do + spree_get :edit, { id: taxon.id } + + expect(response).to have_http_status :ok + end + + it "open taxon edit form" do + spree_get :new + + expect(response).to have_http_status :ok + end + + context "create" do + it "persist data with valid attributes" do + spree_post :create, valid_attributes + + expect(Spree::Taxon.last.name).to eq valid_attributes[:name] + expect(response).to have_http_status :found + end + + it "returns error with invalid attributes" do + spree_post :create, { name: '' } + + expect(Spree::Taxon.count).to eq 2 + expect(response).to have_http_status :unprocessable_entity + end + end + + context "update" do + let!(:new_taxon) { create(:taxon, valid_attributes) } + it "persist data with valid attributes" do + spree_post :update, id: new_taxon.id, + taxon: valid_attributes.merge({ name: 'Taxon name updated' }) + + expect(new_taxon.reload.name).to eq 'Taxon name updated' + expect(response).to have_http_status :found + end + + it "retruns error with invalid attributes" do + spree_post :update, id: new_taxon.id, + taxon: { **valid_attributes, name: '' } + + expect(new_taxon.reload.name).to eq valid_attributes[:name] + expect(response).to have_http_status :unprocessable_entity + end end end end diff --git a/spec/factories/taxon_factory.rb b/spec/factories/taxon_factory.rb index fa8bae12d3..2344e35fe5 100644 --- a/spec/factories/taxon_factory.rb +++ b/spec/factories/taxon_factory.rb @@ -3,7 +3,5 @@ FactoryBot.define do factory :taxon, class: Spree::Taxon do name { 'Ruby on Rails' } - taxonomy - parent_id { nil } end end diff --git a/spec/factories/taxonomy_factory.rb b/spec/factories/taxonomy_factory.rb deleted file mode 100644 index eafe9f8a61..0000000000 --- a/spec/factories/taxonomy_factory.rb +++ /dev/null @@ -1,7 +0,0 @@ -# frozen_string_literal: true - -FactoryBot.define do - factory :taxonomy, class: Spree::Taxonomy do - name { 'Brand' } - end -end diff --git a/spec/models/spree/ability_spec.rb b/spec/models/spree/ability_spec.rb index aa31506e30..f8ed7b32f6 100644 --- a/spec/models/spree/ability_spec.rb +++ b/spec/models/spree/ability_spec.rb @@ -153,13 +153,6 @@ RSpec.describe Spree::Ability do end end - context 'for Taxonomy' do - let(:resource) { Spree::Taxonomy.new } - context 'requested by any user' do - it_should_behave_like 'read only' - end - end - context 'for User' do context 'requested by same user' do let(:resource) { user } diff --git a/spec/models/spree/taxon_spec.rb b/spec/models/spree/taxon_spec.rb index 49797f69f4..fef772576f 100644 --- a/spec/models/spree/taxon_spec.rb +++ b/spec/models/spree/taxon_spec.rb @@ -63,41 +63,6 @@ module Spree taxon.set_permalink expect(taxon.permalink).to eq 'ni-hao' end - - context "with parent taxon" do - before do - allow(taxon).to receive_messages parent_id: 123 - allow(taxon).to receive_messages parent: build_stubbed(:taxon, permalink: "brands") - end - - it "should set permalink correctly when taxon has parent" do - taxon.set_permalink - expect(taxon.permalink).to eq "brands/ruby-on-rails" - end - - it "should set permalink correctly with existing permalink present" do - taxon.permalink = "b/rubyonrails" - taxon.set_permalink - expect(taxon.permalink).to eq "brands/rubyonrails" - end - - it "should support Chinese characters" do - taxon.name = "我" - taxon.set_permalink - expect(taxon.permalink).to eq "brands/wo" - end - end - end - - # Regression test for Spree #2620 - context "creating a child node using first_or_create" do - let(:taxonomy) { create(:taxonomy) } - - it "does not error out" do - expect { - taxonomy.root.children.where(name: "Some name").first_or_create - }.not_to raise_error - end end end end diff --git a/spec/models/spree/taxonomy_spec.rb b/spec/models/spree/taxonomy_spec.rb deleted file mode 100644 index 1ace138313..0000000000 --- a/spec/models/spree/taxonomy_spec.rb +++ /dev/null @@ -1,19 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Spree::Taxonomy do - context "#destroy" do - before do - @taxonomy = create(:taxonomy) - @root_taxon = @taxonomy.root - @child_taxon = create(:taxon, taxonomy_id: @taxonomy.id, parent: @root_taxon) - end - - it "should destroy all associated taxons" do - @taxonomy.destroy - expect{ Spree::Taxon.find(@root_taxon.id) }.to raise_error(ActiveRecord::RecordNotFound) - expect{ Spree::Taxon.find(@child_taxon.id) }.to raise_error(ActiveRecord::RecordNotFound) - end - end -end diff --git a/spec/queries/product_scope_query_spec.rb b/spec/queries/product_scope_query_spec.rb index 27f2ade0cf..24fa18ff99 100755 --- a/spec/queries/product_scope_query_spec.rb +++ b/spec/queries/product_scope_query_spec.rb @@ -13,7 +13,9 @@ RSpec.describe ProductScopeQuery do before { current_api_user.enterprise_roles.create(enterprise: supplier2) } describe '#bulk_products' do - let!(:product3) { create(:product, supplier_id: supplier2.id) } + let!(:product3) { + create(:product, supplier_id: supplier2.id, primary_taxon_id: create(:taxon).id) + } it "returns a list of products" do expect(ProductScopeQuery.new(current_api_user, {}).bulk_products) diff --git a/spec/system/admin/configuration/taxonomies_spec.rb b/spec/system/admin/configuration/taxonomies_spec.rb deleted file mode 100644 index 5f4771cbd6..0000000000 --- a/spec/system/admin/configuration/taxonomies_spec.rb +++ /dev/null @@ -1,59 +0,0 @@ -# frozen_string_literal: true - -require 'system_helper' - -RSpec.describe "Taxonomies" do - include AuthenticationHelper - include WebHelper - - before(:each) do - login_as_admin - visit spree.edit_admin_general_settings_path - end - - context "show" do - it "should display existing taxonomies" do - create(:taxonomy, name: 'Brand') - create(:taxonomy, name: 'Categories') - click_link "Taxonomies" - within("table.index tbody") do - expect(page).to have_content("Brand") - expect(page).to have_content("Categories") - end - end - end - - context "create" do - before(:each) do - click_link "Taxonomies" - click_link "admin_new_taxonomy_link" - end - - it "should allow an admin to create a new taxonomy" do - expect(page).to have_content("New Taxonomy") - fill_in "taxonomy_name", with: "sports" - click_button "Create" - expect(page).to have_content("successfully created!") - end - - it "should display validation errors" do - fill_in "taxonomy_name", with: "" - click_button "Create" - expect(page).to have_content("can't be blank") - end - end - - context "edit" do - it "should allow an admin to update an existing taxonomy" do - create(:taxonomy) - click_link "Taxonomies" - within_row(1) { find(".icon-edit").click } - fill_in "taxonomy_name", with: "sports 99" - sleep 1 - click_button "Update" - expect(page).to have_current_path spree.admin_taxonomies_path - expect(page).to have_content("successfully updated!") - expect(page).to have_content("sports 99") - end - end -end diff --git a/spec/system/consumer/shopping/products_spec.rb b/spec/system/consumer/shopping/products_spec.rb index f2ff5b0d5a..87d155c0ec 100644 --- a/spec/system/consumer/shopping/products_spec.rb +++ b/spec/system/consumer/shopping/products_spec.rb @@ -130,7 +130,7 @@ RSpec.describe "As a consumer I want to view products" do add_variant_to_order_cycle(exchange1, variant2) end - context "product taxonomies" do + context "product taxons" do before do distributor.preferred_shopfront_product_sorting_method = "by_category" distributor.preferred_shopfront_taxon_order = taxon.id.to_s From 49aa9e0768ae694668c2fc702ec569924e0e359e Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Fri, 9 Aug 2024 11:11:01 +1000 Subject: [PATCH 032/206] Make taxonomy migration reversible --- .../20240806135838_drop_spree_taxonomies_table.rb | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/db/migrate/20240806135838_drop_spree_taxonomies_table.rb b/db/migrate/20240806135838_drop_spree_taxonomies_table.rb index 2a3fdd3d8f..99a24f1ad8 100644 --- a/db/migrate/20240806135838_drop_spree_taxonomies_table.rb +++ b/db/migrate/20240806135838_drop_spree_taxonomies_table.rb @@ -1,14 +1,19 @@ class DropSpreeTaxonomiesTable < ActiveRecord::Migration[7.0] def change # Remove columns - remove_column :spree_taxons, :lft - remove_column :spree_taxons, :rgt + remove_column :spree_taxons, :lft, :integer + remove_column :spree_taxons, :rgt, :integer # Remove references - remove_reference :spree_taxons, :parent, index: true, foriegn_key: true - remove_reference :spree_taxons, :taxonomy, index: true, foriegn_key: true + remove_reference :spree_taxons, :parent, index: true, foreign_key: { to_table: :spree_taxons } + remove_reference :spree_taxons, :taxonomy, index: true, foreign_key: { to_table: :spree_taxonomies } # Drop table - drop_table :spree_taxonomies + drop_table :spree_taxonomies, id: :serial, force: :cascade do |t| + t.string "name", limit: 255, null: false + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + t.integer "position", default: 0 + end end end From 82b742608d88ef9c52e7d036c02d6088734f0dc8 Mon Sep 17 00:00:00 2001 From: wandji20 Date: Fri, 9 Aug 2024 16:03:36 +0100 Subject: [PATCH 033/206] Remove jquery/js.tree plugin [OFN-11636] --- app/assets/javascripts/admin/all.js | 1 - .../jquery.jstree/jquery.jstree.js | 4540 ----------------- .../jquery.jstree/themes/apple/bg.jpg | Bin 312 -> 0 bytes .../jquery.jstree/themes/apple/d.png | Bin 4398 -> 0 bytes .../jquery.jstree/themes/apple/dot_for_ie.gif | Bin 43 -> 0 bytes .../jquery.jstree/themes/apple/style.css | 60 - .../jquery.jstree/themes/apple/throbber.gif | Bin 1435 -> 0 bytes 7 files changed, 4601 deletions(-) delete mode 100755 vendor/assets/javascripts/jquery.jstree/jquery.jstree.js delete mode 100755 vendor/assets/javascripts/jquery.jstree/themes/apple/bg.jpg delete mode 100755 vendor/assets/javascripts/jquery.jstree/themes/apple/d.png delete mode 100755 vendor/assets/javascripts/jquery.jstree/themes/apple/dot_for_ie.gif delete mode 100644 vendor/assets/javascripts/jquery.jstree/themes/apple/style.css delete mode 100755 vendor/assets/javascripts/jquery.jstree/themes/apple/throbber.gif diff --git a/app/assets/javascripts/admin/all.js b/app/assets/javascripts/admin/all.js index 7c89801bcd..60476e4db2 100644 --- a/app/assets/javascripts/admin/all.js +++ b/app/assets/javascripts/admin/all.js @@ -10,7 +10,6 @@ //= require jquery.ui.all //= require jquery.powertip //= require jquery.cookie -//= require jquery.jstree/jquery.jstree //= require jquery.vAlign //= require angular //= require angular-resource diff --git a/vendor/assets/javascripts/jquery.jstree/jquery.jstree.js b/vendor/assets/javascripts/jquery.jstree/jquery.jstree.js deleted file mode 100755 index d5d2ea9155..0000000000 --- a/vendor/assets/javascripts/jquery.jstree/jquery.jstree.js +++ /dev/null @@ -1,4540 +0,0 @@ -/* - * jsTree 1.0-rc3 - * http://jstree.com/ - * - * Copyright (c) 2010 Ivan Bozhanov (vakata.com) - * - * Licensed same as jquery - under the terms of either the MIT License or the GPL Version 2 License - * http://www.opensource.org/licenses/mit-license.php - * http://www.gnu.org/licenses/gpl.html - * - * $Date: 2011-02-09 01:17:14 +0200 (ср, 09 февр 2011) $ - * $Revision: 236 $ - */ - -/*jslint browser: true, onevar: true, undef: true, bitwise: true, strict: true */ -/*global window : false, clearInterval: false, clearTimeout: false, document: false, setInterval: false, setTimeout: false, jQuery: false, navigator: false, XSLTProcessor: false, DOMParser: false, XMLSerializer: false*/ - -"use strict"; - -// top wrapper to prevent multiple inclusion (is this OK?) -(function () { if(jQuery && jQuery.jstree) { return; } - var is_ie6 = false, is_ie7 = false, is_ff2 = false; - -/* - * jsTree core - */ -(function ($) { - // Common functions not related to jsTree - // decided to move them to a `vakata` "namespace" - $.vakata = {}; - // CSS related functions - $.vakata.css = { - get_css : function(rule_name, delete_flag, sheet) { - rule_name = rule_name.toLowerCase(); - var css_rules = sheet.cssRules || sheet.rules, - j = 0; - do { - if(css_rules.length && j > css_rules.length + 5) { return false; } - if(css_rules[j].selectorText && css_rules[j].selectorText.toLowerCase() == rule_name) { - if(delete_flag === true) { - if(sheet.removeRule) { sheet.removeRule(j); } - if(sheet.deleteRule) { sheet.deleteRule(j); } - return true; - } - else { return css_rules[j]; } - } - } - while (css_rules[++j]); - return false; - }, - add_css : function(rule_name, sheet) { - if($.jstree.css.get_css(rule_name, false, sheet)) { return false; } - if(sheet.insertRule) { sheet.insertRule(rule_name + ' { }', 0); } else { sheet.addRule(rule_name, null, 0); } - return $.vakata.css.get_css(rule_name); - }, - remove_css : function(rule_name, sheet) { - return $.vakata.css.get_css(rule_name, true, sheet); - }, - add_sheet : function(opts) { - var tmp = false, is_new = true; - if(opts.str) { - if(opts.title) { tmp = $("style[id='" + opts.title + "-stylesheet']")[0]; } - if(tmp) { is_new = false; } - else { - tmp = document.createElement("style"); - tmp.setAttribute('type',"text/css"); - if(opts.title) { tmp.setAttribute("id", opts.title + "-stylesheet"); } - } - if(tmp.styleSheet) { - if(is_new) { - document.getElementsByTagName("head")[0].appendChild(tmp); - tmp.styleSheet.cssText = opts.str; - } - else { - tmp.styleSheet.cssText = tmp.styleSheet.cssText + " " + opts.str; - } - } - else { - tmp.appendChild(document.createTextNode(opts.str)); - document.getElementsByTagName("head")[0].appendChild(tmp); - } - return tmp.sheet || tmp.styleSheet; - } - if(opts.url) { - if(document.createStyleSheet) { - try { tmp = document.createStyleSheet(opts.url); } catch (e) { } - } - else { - tmp = document.createElement('link'); - tmp.rel = 'stylesheet'; - tmp.type = 'text/css'; - tmp.media = "all"; - tmp.href = opts.url; - document.getElementsByTagName("head")[0].appendChild(tmp); - return tmp.styleSheet; - } - } - } - }; - - // private variables - var instances = [], // instance array (used by $.jstree.reference/create/focused) - focused_instance = -1, // the index in the instance array of the currently focused instance - plugins = {}, // list of included plugins - prepared_move = {}; // for the move_node function - - // jQuery plugin wrapper (thanks to jquery UI widget function) - $.fn.jstree = function (settings) { - var isMethodCall = (typeof settings == 'string'), // is this a method call like $().jstree("open_node") - args = Array.prototype.slice.call(arguments, 1), - returnValue = this; - - // if a method call execute the method on all selected instances - if(isMethodCall) { - if(settings.substring(0, 1) == '_') { return returnValue; } - this.each(function() { - var instance = instances[$.data(this, "jstree-instance-id")], - methodValue = (instance && $.isFunction(instance[settings])) ? instance[settings].apply(instance, args) : instance; - if(typeof methodValue !== "undefined" && (settings.indexOf("is_") === 0 || (methodValue !== true && methodValue !== false))) { returnValue = methodValue; return false; } - }); - } - else { - this.each(function() { - // extend settings and allow for multiple hashes and $.data - var instance_id = $.data(this, "jstree-instance-id"), - a = [], - b = settings ? $.extend({}, true, settings) : {}, - c = $(this), - s = false, - t = []; - a = a.concat(args); - if(c.data("jstree")) { a.push(c.data("jstree")); } - b = a.length ? $.extend.apply(null, [true, b].concat(a)) : b; - - // if an instance already exists, destroy it first - if(typeof instance_id !== "undefined" && instances[instance_id]) { instances[instance_id].destroy(); } - // push a new empty object to the instances array - instance_id = parseInt(instances.push({}),10) - 1; - // store the jstree instance id to the container element - $.data(this, "jstree-instance-id", instance_id); - // clean up all plugins - b.plugins = $.isArray(b.plugins) ? b.plugins : $.jstree.defaults.plugins.slice(); - b.plugins.unshift("core"); - // only unique plugins - b.plugins = b.plugins.sort().join(",,").replace(/(,|^)([^,]+)(,,\2)+(,|$)/g,"$1$2$4").replace(/,,+/g,",").replace(/,$/,"").split(","); - - // extend defaults with passed data - s = $.extend(true, {}, $.jstree.defaults, b); - s.plugins = b.plugins; - $.each(plugins, function (i, val) { - if($.inArray(i, s.plugins) === -1) { s[i] = null; delete s[i]; } - else { t.push(i); } - }); - s.plugins = t; - - // push the new object to the instances array (at the same time set the default classes to the container) and init - instances[instance_id] = new $.jstree._instance(instance_id, $(this).addClass("jstree jstree-" + instance_id), s); - // init all activated plugins for this instance - $.each(instances[instance_id]._get_settings().plugins, function (i, val) { instances[instance_id].data[val] = {}; }); - $.each(instances[instance_id]._get_settings().plugins, function (i, val) { if(plugins[val]) { plugins[val].__init.apply(instances[instance_id]); } }); - // initialize the instance - setTimeout(function() { instances[instance_id].init(); }, 0); - }); - } - // return the jquery selection (or if it was a method call that returned a value - the returned value) - return returnValue; - }; - // object to store exposed functions and objects - $.jstree = { - defaults : { - plugins : [] - }, - _focused : function () { return instances[focused_instance] || null; }, - _reference : function (needle) { - // get by instance id - if(instances[needle]) { return instances[needle]; } - // get by DOM (if still no luck - return null - var o = $(needle); - if(!o.length && typeof needle === "string") { o = $("#" + needle); } - if(!o.length) { return null; } - return instances[o.closest(".jstree").data("jstree-instance-id")] || null; - }, - _instance : function (index, container, settings) { - // for plugins to store data in - this.data = { core : {} }; - this.get_settings = function () { return $.extend(true, {}, settings); }; - this._get_settings = function () { return settings; }; - this.get_index = function () { return index; }; - this.get_container = function () { return container; }; - this.get_container_ul = function () { return container.children("ul:eq(0)"); }; - this._set_settings = function (s) { - settings = $.extend(true, {}, settings, s); - }; - }, - _fn : { }, - plugin : function (pname, pdata) { - pdata = $.extend({}, { - __init : $.noop, - __destroy : $.noop, - _fn : {}, - defaults : false - }, pdata); - plugins[pname] = pdata; - - $.jstree.defaults[pname] = pdata.defaults; - $.each(pdata._fn, function (i, val) { - val.plugin = pname; - val.old = $.jstree._fn[i]; - $.jstree._fn[i] = function () { - var rslt, - func = val, - args = Array.prototype.slice.call(arguments), - evnt = new $.Event("before.jstree"), - rlbk = false; - - if(this.data.core.locked === true && i !== "unlock" && i !== "is_locked") { return; } - - // Check if function belongs to the included plugins of this instance - do { - if(func && func.plugin && $.inArray(func.plugin, this._get_settings().plugins) !== -1) { break; } - func = func.old; - } while(func); - if(!func) { return; } - - // context and function to trigger events, then finally call the function - if(i.indexOf("_") === 0) { - rslt = func.apply(this, args); - } - else { - rslt = this.get_container().triggerHandler(evnt, { "func" : i, "inst" : this, "args" : args, "plugin" : func.plugin }); - if(rslt === false) { return; } - if(typeof rslt !== "undefined") { args = rslt; } - - rslt = func.apply( - $.extend({}, this, { - __callback : function (data) { - this.get_container().triggerHandler( i + '.jstree', { "inst" : this, "args" : args, "rslt" : data, "rlbk" : rlbk }); - }, - __rollback : function () { - rlbk = this.get_rollback(); - return rlbk; - }, - __call_old : function (replace_arguments) { - return func.old.apply(this, (replace_arguments ? Array.prototype.slice.call(arguments, 1) : args ) ); - } - }), args); - } - - // return the result - return rslt; - }; - $.jstree._fn[i].old = val.old; - $.jstree._fn[i].plugin = pname; - }); - }, - rollback : function (rb) { - if(rb) { - if(!$.isArray(rb)) { rb = [ rb ]; } - $.each(rb, function (i, val) { - instances[val.i].set_rollback(val.h, val.d); - }); - } - } - }; - // set the prototype for all instances - $.jstree._fn = $.jstree._instance.prototype = {}; - - // load the css when DOM is ready - $(function() { - // code is copied from jQuery ($.browser is deprecated + there is a bug in IE) - var u = navigator.userAgent.toLowerCase(), - v = (u.match( /.+?(?:rv|it|ra|ie)[\/: ]([\d.]+)/ ) || [0,'0'])[1], - css_string = '' + - '.jstree ul, .jstree li { display:block; margin:0 0 0 0; padding:0 0 0 0; list-style-type:none; } ' + - '.jstree li { display:block; min-height:18px; line-height:18px; white-space:nowrap; margin-left:18px; min-width:18px; } ' + - '.jstree-rtl li { margin-left:0; margin-right:18px; } ' + - '.jstree > ul > li { margin-left:0px; } ' + - '.jstree-rtl > ul > li { margin-right:0px; } ' + - '.jstree ins { display:inline-block; text-decoration:none; width:18px; height:18px; margin:0 0 0 0; padding:0; } ' + - '.jstree a { display:inline-block; line-height:16px; height:16px; color:black; white-space:nowrap; text-decoration:none; padding:1px 2px; margin:0; } ' + - '.jstree a:focus { outline: none; } ' + - '.jstree a > ins { height:16px; width:16px; } ' + - '.jstree a > .jstree-icon { margin-right:3px; } ' + - '.jstree-rtl a > .jstree-icon { margin-left:3px; margin-right:0; } ' + - 'li.jstree-open > ul { display:block; } ' + - 'li.jstree-closed > ul { display:none; } '; - // Correct IE 6 (does not support the > CSS selector) - if(/msie/.test(u) && parseInt(v, 10) == 6) { - is_ie6 = true; - - // fix image flicker and lack of caching - try { - document.execCommand("BackgroundImageCache", false, true); - } catch (err) { } - - css_string += '' + - '.jstree li { height:18px; margin-left:0; margin-right:0; } ' + - '.jstree li li { margin-left:18px; } ' + - '.jstree-rtl li li { margin-left:0px; margin-right:18px; } ' + - 'li.jstree-open ul { display:block; } ' + - 'li.jstree-closed ul { display:none !important; } ' + - '.jstree li a { display:inline; border-width:0 !important; padding:0px 2px !important; } ' + - '.jstree li a ins { height:16px; width:16px; margin-right:3px; } ' + - '.jstree-rtl li a ins { margin-right:0px; margin-left:3px; } '; - } - // Correct IE 7 (shifts anchor nodes onhover) - if(/msie/.test(u) && parseInt(v, 10) == 7) { - is_ie7 = true; - css_string += '.jstree li a { border-width:0 !important; padding:0px 2px !important; } '; - } - // correct ff2 lack of display:inline-block - if(!/compatible/.test(u) && /mozilla/.test(u) && parseFloat(v, 10) < 1.9) { - is_ff2 = true; - css_string += '' + - '.jstree ins { display:-moz-inline-box; } ' + - '.jstree li { line-height:12px; } ' + // WHY?? - '.jstree a { display:-moz-inline-box; } ' + - '.jstree .jstree-no-icons .jstree-checkbox { display:-moz-inline-stack !important; } '; - /* this shouldn't be here as it is theme specific */ - } - // the default stylesheet - $.vakata.css.add_sheet({ str : css_string, title : "jstree" }); - }); - - // core functions (open, close, create, update, delete) - $.jstree.plugin("core", { - __init : function () { - this.data.core.locked = false; - this.data.core.to_open = this.get_settings().core.initially_open; - this.data.core.to_load = this.get_settings().core.initially_load; - }, - defaults : { - html_titles : false, - animation : 500, - initially_open : [], - initially_load : [], - open_parents : true, - notify_plugins : true, - rtl : false, - load_open : false, - strings : { - loading : "Loading ...", - new_node : "New node", - multiple_selection : "Multiple selection" - } - }, - _fn : { - init : function () { - this.set_focus(); - if(this._get_settings().core.rtl) { - this.get_container().addClass("jstree-rtl").css("direction", "rtl"); - } - this.get_container().html(""); - this.data.core.li_height = this.get_container_ul().find("li.jstree-closed, li.jstree-leaf").eq(0).height() || 18; - - this.get_container() - .delegate("li > ins", "click.jstree", $.proxy(function (event) { - var trgt = $(event.target); - if(trgt.is("ins") && event.pageY - trgt.offset().top < this.data.core.li_height) { this.toggle_node(trgt); } - }, this)) - .bind("mousedown.jstree", $.proxy(function () { - this.set_focus(); // This used to be setTimeout(set_focus,0) - why? - }, this)) - .bind("dblclick.jstree", function (event) { - var sel; - if(document.selection && document.selection.empty) { document.selection.empty(); } - else { - if(window.getSelection) { - sel = window.getSelection(); - try { - sel.removeAllRanges(); - sel.collapse(); - } catch (err) { } - } - } - }); - if(this._get_settings().core.notify_plugins) { - this.get_container() - .bind("load_node.jstree", $.proxy(function (e, data) { - var o = this._get_node(data.rslt.obj), - t = this; - if(o === -1) { o = this.get_container_ul(); } - if(!o.length) { return; } - o.find("li").each(function () { - var th = $(this); - if(th.data("jstree")) { - $.each(th.data("jstree"), function (plugin, values) { - if(t.data[plugin] && $.isFunction(t["_" + plugin + "_notify"])) { - t["_" + plugin + "_notify"].call(t, th, values); - } - }); - } - }); - }, this)); - } - if(this._get_settings().core.load_open) { - this.get_container() - .bind("load_node.jstree", $.proxy(function (e, data) { - var o = this._get_node(data.rslt.obj), - t = this; - if(o === -1) { o = this.get_container_ul(); } - if(!o.length) { return; } - o.find("li.jstree-open:not(:has(ul))").each(function () { - t.load_node(this, $.noop, $.noop); - }); - }, this)); - } - this.__callback(); - this.load_node(-1, function () { this.loaded(); this.reload_nodes(); }); - }, - destroy : function () { - var i, - n = this.get_index(), - s = this._get_settings(), - _this = this; - - $.each(s.plugins, function (i, val) { - try { plugins[val].__destroy.apply(_this); } catch(err) { } - }); - this.__callback(); - // set focus to another instance if this one is focused - if(this.is_focused()) { - for(i in instances) { - if(instances.hasOwnProperty(i) && i != n) { - instances[i].set_focus(); - break; - } - } - } - // if no other instance found - if(n === focused_instance) { focused_instance = -1; } - // remove all traces of jstree in the DOM (only the ones set using jstree*) and cleans all events - this.get_container() - .unbind(".jstree") - .undelegate(".jstree") - .removeData("jstree-instance-id") - .find("[class^='jstree']") - .andSelf() - .attr("class", function () { return this.className.replace(/jstree[^ ]*|$/ig,''); }); - $(document) - .unbind(".jstree-" + n) - .undelegate(".jstree-" + n); - // remove the actual data - instances[n] = null; - delete instances[n]; - }, - - _core_notify : function (n, data) { - if(data.opened) { - this.open_node(n, false, true); - } - }, - - lock : function () { - this.data.core.locked = true; - this.get_container().children("ul").addClass("jstree-locked").css("opacity","0.7"); - this.__callback({}); - }, - unlock : function () { - this.data.core.locked = false; - this.get_container().children("ul").removeClass("jstree-locked").css("opacity","1"); - this.__callback({}); - }, - is_locked : function () { return this.data.core.locked; }, - save_opened : function () { - var _this = this; - this.data.core.to_open = []; - this.get_container_ul().find("li.jstree-open").each(function () { - if(this.id) { _this.data.core.to_open.push("#" + this.id.toString().replace(/^#/,"").replace(/\\\//g,"/").replace(/\//g,"\\\/").replace(/\\\./g,".").replace(/\./g,"\\.").replace(/\:/g,"\\:")); } - }); - this.__callback(_this.data.core.to_open); - }, - save_loaded : function () { }, - reload_nodes : function (is_callback) { - var _this = this, - done = true, - current = [], - remaining = []; - if(!is_callback) { - this.data.core.reopen = false; - this.data.core.refreshing = true; - this.data.core.to_open = $.map($.makeArray(this.data.core.to_open), function (n) { return "#" + n.toString().replace(/^#/,"").replace(/\\\//g,"/").replace(/\//g,"\\\/").replace(/\\\./g,".").replace(/\./g,"\\.").replace(/\:/g,"\\:"); }); - this.data.core.to_load = $.map($.makeArray(this.data.core.to_load), function (n) { return "#" + n.toString().replace(/^#/,"").replace(/\\\//g,"/").replace(/\//g,"\\\/").replace(/\\\./g,".").replace(/\./g,"\\.").replace(/\:/g,"\\:"); }); - if(this.data.core.to_open.length) { - this.data.core.to_load = this.data.core.to_load.concat(this.data.core.to_open); - } - } - if(this.data.core.to_load.length) { - $.each(this.data.core.to_load, function (i, val) { - if(val == "#") { return true; } - if($(val).length) { current.push(val); } - else { remaining.push(val); } - }); - if(current.length) { - this.data.core.to_load = remaining; - $.each(current, function (i, val) { - if(!_this._is_loaded(val)) { - _this.load_node(val, function () { _this.reload_nodes(true); }, function () { _this.reload_nodes(true); }); - done = false; - } - }); - } - } - if(this.data.core.to_open.length) { - $.each(this.data.core.to_open, function (i, val) { - _this.open_node(val, false, true); - }); - } - if(done) { - // TODO: find a more elegant approach to syncronizing returning requests - if(this.data.core.reopen) { clearTimeout(this.data.core.reopen); } - this.data.core.reopen = setTimeout(function () { _this.__callback({}, _this); }, 50); - this.data.core.refreshing = false; - this.reopen(); - } - }, - reopen : function () { - var _this = this; - if(this.data.core.to_open.length) { - $.each(this.data.core.to_open, function (i, val) { - _this.open_node(val, false, true); - }); - } - this.__callback({}); - }, - refresh : function (obj) { - var _this = this; - this.save_opened(); - if(!obj) { obj = -1; } - obj = this._get_node(obj); - if(!obj) { obj = -1; } - if(obj !== -1) { obj.children("UL").remove(); } - else { this.get_container_ul().empty(); } - this.load_node(obj, function () { _this.__callback({ "obj" : obj}); _this.reload_nodes(); }); - }, - // Dummy function to fire after the first load (so that there is a jstree.loaded event) - loaded : function () { - this.__callback(); - }, - // deal with focus - set_focus : function () { - if(this.is_focused()) { return; } - var f = $.jstree._focused(); - if(f) { f.unset_focus(); } - - this.get_container().addClass("jstree-focused"); - focused_instance = this.get_index(); - this.__callback(); - }, - is_focused : function () { - return focused_instance == this.get_index(); - }, - unset_focus : function () { - if(this.is_focused()) { - this.get_container().removeClass("jstree-focused"); - focused_instance = -1; - } - this.__callback(); - }, - - // traverse - _get_node : function (obj) { - var $obj = $(obj, this.get_container()); - if($obj.is(".jstree") || obj == -1) { return -1; } - $obj = $obj.closest("li", this.get_container()); - return $obj.length ? $obj : false; - }, - _get_next : function (obj, strict) { - obj = this._get_node(obj); - if(obj === -1) { return this.get_container().find("> ul > li:first-child"); } - if(!obj.length) { return false; } - if(strict) { return (obj.nextAll("li").size() > 0) ? obj.nextAll("li:eq(0)") : false; } - - if(obj.hasClass("jstree-open")) { return obj.find("li:eq(0)"); } - else if(obj.nextAll("li").size() > 0) { return obj.nextAll("li:eq(0)"); } - else { return obj.parentsUntil(".jstree","li").next("li").eq(0); } - }, - _get_prev : function (obj, strict) { - obj = this._get_node(obj); - if(obj === -1) { return this.get_container().find("> ul > li:last-child"); } - if(!obj.length) { return false; } - if(strict) { return (obj.prevAll("li").length > 0) ? obj.prevAll("li:eq(0)") : false; } - - if(obj.prev("li").length) { - obj = obj.prev("li").eq(0); - while(obj.hasClass("jstree-open")) { obj = obj.children("ul:eq(0)").children("li:last"); } - return obj; - } - else { var o = obj.parentsUntil(".jstree","li:eq(0)"); return o.length ? o : false; } - }, - _get_parent : function (obj) { - obj = this._get_node(obj); - if(obj == -1 || !obj.length) { return false; } - var o = obj.parentsUntil(".jstree", "li:eq(0)"); - return o.length ? o : -1; - }, - _get_children : function (obj) { - obj = this._get_node(obj); - if(obj === -1) { return this.get_container().children("ul:eq(0)").children("li"); } - if(!obj.length) { return false; } - return obj.children("ul:eq(0)").children("li"); - }, - get_path : function (obj, id_mode) { - var p = [], - _this = this; - obj = this._get_node(obj); - if(obj === -1 || !obj || !obj.length) { return false; } - obj.parentsUntil(".jstree", "li").each(function () { - p.push( id_mode ? this.id : _this.get_text(this) ); - }); - p.reverse(); - p.push( id_mode ? obj.attr("id") : this.get_text(obj) ); - return p; - }, - - // string functions - _get_string : function (key) { - return this._get_settings().core.strings[key] || key; - }, - - is_open : function (obj) { obj = this._get_node(obj); return obj && obj !== -1 && obj.hasClass("jstree-open"); }, - is_closed : function (obj) { obj = this._get_node(obj); return obj && obj !== -1 && obj.hasClass("jstree-closed"); }, - is_leaf : function (obj) { obj = this._get_node(obj); return obj && obj !== -1 && obj.hasClass("jstree-leaf"); }, - correct_state : function (obj) { - obj = this._get_node(obj); - if(!obj || obj === -1) { return false; } - obj.removeClass("jstree-closed jstree-open").addClass("jstree-leaf").children("ul").remove(); - this.__callback({ "obj" : obj }); - }, - // open/close - open_node : function (obj, callback, skip_animation) { - obj = this._get_node(obj); - if(!obj.length) { return false; } - if(!obj.hasClass("jstree-closed")) { if(callback) { callback.call(); } return false; } - var s = skip_animation || is_ie6 ? 0 : this._get_settings().core.animation, - t = this; - if(!this._is_loaded(obj)) { - obj.children("a").addClass("jstree-loading"); - this.load_node(obj, function () { t.open_node(obj, callback, skip_animation); }, callback); - } - else { - if(this._get_settings().core.open_parents) { - obj.parentsUntil(".jstree",".jstree-closed").each(function () { - t.open_node(this, false, true); - }); - } - if(s) { obj.children("ul").css("display","none"); } - obj.removeClass("jstree-closed").addClass("jstree-open").children("a").removeClass("jstree-loading"); - if(s) { obj.children("ul").stop(true, true).slideDown(s, function () { this.style.display = ""; t.after_open(obj); }); } - else { t.after_open(obj); } - this.__callback({ "obj" : obj }); - if(callback) { callback.call(); } - } - }, - after_open : function (obj) { this.__callback({ "obj" : obj }); }, - close_node : function (obj, skip_animation) { - obj = this._get_node(obj); - var s = skip_animation || is_ie6 ? 0 : this._get_settings().core.animation, - t = this; - if(!obj.length || !obj.hasClass("jstree-open")) { return false; } - if(s) { obj.children("ul").attr("style","display:block !important"); } - obj.removeClass("jstree-open").addClass("jstree-closed"); - if(s) { obj.children("ul").stop(true, true).slideUp(s, function () { this.style.display = ""; t.after_close(obj); }); } - else { t.after_close(obj); } - this.__callback({ "obj" : obj }); - }, - after_close : function (obj) { this.__callback({ "obj" : obj }); }, - toggle_node : function (obj) { - obj = this._get_node(obj); - if(obj.hasClass("jstree-closed")) { return this.open_node(obj); } - if(obj.hasClass("jstree-open")) { return this.close_node(obj); } - }, - open_all : function (obj, do_animation, original_obj) { - obj = obj ? this._get_node(obj) : -1; - if(!obj || obj === -1) { obj = this.get_container_ul(); } - if(original_obj) { - obj = obj.find("li.jstree-closed"); - } - else { - original_obj = obj; - if(obj.is(".jstree-closed")) { obj = obj.find("li.jstree-closed").andSelf(); } - else { obj = obj.find("li.jstree-closed"); } - } - var _this = this; - obj.each(function () { - var __this = this; - if(!_this._is_loaded(this)) { _this.open_node(this, function() { _this.open_all(__this, do_animation, original_obj); }, !do_animation); } - else { _this.open_node(this, false, !do_animation); } - }); - // so that callback is fired AFTER all nodes are open - if(original_obj.find('li.jstree-closed').length === 0) { this.__callback({ "obj" : original_obj }); } - }, - close_all : function (obj, do_animation) { - var _this = this; - obj = obj ? this._get_node(obj) : this.get_container(); - if(!obj || obj === -1) { obj = this.get_container_ul(); } - obj.find("li.jstree-open").andSelf().each(function () { _this.close_node(this, !do_animation); }); - this.__callback({ "obj" : obj }); - }, - clean_node : function (obj) { - obj = obj && obj != -1 ? $(obj) : this.get_container_ul(); - obj = obj.is("li") ? obj.find("li").andSelf() : obj.find("li"); - obj.removeClass("jstree-last") - .filter("li:last-child").addClass("jstree-last").end() - .filter(":has(li)") - .not(".jstree-open").removeClass("jstree-leaf").addClass("jstree-closed"); - obj.not(".jstree-open, .jstree-closed").addClass("jstree-leaf").children("ul").remove(); - this.__callback({ "obj" : obj }); - }, - // rollback - get_rollback : function () { - this.__callback(); - return { i : this.get_index(), h : this.get_container().children("ul").clone(true), d : this.data }; - }, - set_rollback : function (html, data) { - this.get_container().empty().append(html); - this.data = data; - this.__callback(); - }, - // Dummy functions to be overwritten by any datastore plugin included - load_node : function (obj, s_call, e_call) { this.__callback({ "obj" : obj }); }, - _is_loaded : function (obj) { return true; }, - - // Basic operations: create - create_node : function (obj, position, js, callback, is_loaded) { - obj = this._get_node(obj); - position = typeof position === "undefined" ? "last" : position; - var d = $("
  • "), - s = this._get_settings().core, - tmp; - - if(obj !== -1 && !obj.length) { return false; } - if(!is_loaded && !this._is_loaded(obj)) { this.load_node(obj, function () { this.create_node(obj, position, js, callback, true); }); return false; } - - this.__rollback(); - - if(typeof js === "string") { js = { "data" : js }; } - if(!js) { js = {}; } - if(js.attr) { d.attr(js.attr); } - if(js.metadata) { d.data(js.metadata); } - if(js.state) { d.addClass("jstree-" + js.state); } - if(!js.data) { js.data = this._get_string("new_node"); } - if(!$.isArray(js.data)) { tmp = js.data; js.data = []; js.data.push(tmp); } - $.each(js.data, function (i, m) { - tmp = $(""); - if($.isFunction(m)) { m = m.call(this, js); } - if(typeof m == "string") { tmp.attr('href','#')[ s.html_titles ? "html" : "text" ](m); } - else { - if(!m.attr) { m.attr = {}; } - if(!m.attr.href) { m.attr.href = '#'; } - tmp.attr(m.attr)[ s.html_titles ? "html" : "text" ](m.title); - if(m.language) { tmp.addClass(m.language); } - } - tmp.prepend(" "); - if(m.icon) { - if(m.icon.indexOf("/") === -1) { tmp.children("ins").addClass(m.icon); } - else { tmp.children("ins").css("background","url('" + m.icon + "') center center no-repeat"); } - } - d.append(tmp); - }); - d.prepend(" "); - if(obj === -1) { - obj = this.get_container(); - if(position === "before") { position = "first"; } - if(position === "after") { position = "last"; } - } - switch(position) { - case "before": obj.before(d); tmp = this._get_parent(obj); break; - case "after" : obj.after(d); tmp = this._get_parent(obj); break; - case "inside": - case "first" : - if(!obj.children("ul").length) { obj.append("